// Copyright (c) 2019-2025 Christoffer Lerno. All rights reserved.
// Use of this source code is governed by the GNU LGPLv3.0 license
// a copy of which can be found in the LICENSE file.

#include "sema_internal.h"
#include <math.h>

#include "compiler_tests/benchmark.h"

#define RETURN_SEMA_FUNC_ERROR(_decl, _node, ...) do { sema_error_at(context, (_node)->span, __VA_ARGS__); SEMA_NOTE(_decl, "The definition was here."); return false; } while (0)
#define RETURN_NOTE_FUNC_DEFINITION do { SEMA_NOTE(callee->definition, "The definition was here."); return false; } while (0)
#define RESOLVE(expr__, check__) \
  do { \
  Expr *expr_temp__ = expr__; \
  switch (expr_temp__->resolve_status) { \
    case RESOLVE_NOT_DONE: \
        expr_temp__->resolve_status = RESOLVE_RUNNING; \
        if (!(check__)) return expr_poison(expr_temp__); \
		expr_temp__->resolve_status = RESOLVE_DONE; \
		return true; \
	case RESOLVE_RUNNING: \
		SEMA_ERROR(expr, "Recursive resolution of expression"); \
		return expr_poison(expr_temp__); \
	case RESOLVE_DONE: \
		return expr_ok(expr_temp__); \
	default: \
		UNREACHABLE \
	} } while (0);


typedef struct
{
	bool macro;
	Decl *definition;
	SourceSpan call_location;
	const char *name;
	const char *block_parameter;
	Decl **params;
	Expr *struct_var;
	Signature *signature;
} CalledDecl;

// Properties
static inline BuiltinFunction builtin_by_name(const char *name);
static inline bool sema_expr_analyse_subscript(SemaContext *context, Expr *expr, bool *failed_ref);
static inline bool sema_expr_analyse_pointer_offset(SemaContext *context, Expr *expr);
static inline bool sema_expr_analyse_slice(SemaContext *context, Expr *expr);
static inline bool sema_expr_analyse_access(SemaContext *context, Expr *expr, bool *missing_ref);
static inline bool sema_expr_analyse_compound_literal(SemaContext *context, Expr *expr, bool *no_match_ref);
static inline bool sema_expr_analyse_builtin(SemaContext *context, Expr *expr, bool throw_error);
static inline bool sema_expr_analyse_binary(SemaContext *context, Type *infer_type, Expr *expr, bool *failed_ref);
static inline bool sema_expr_resolve_ct_eval(SemaContext *context, Expr *expr);
static inline bool sema_expr_analyse_identifier(SemaContext *context, Type *to, Expr *expr);
static inline bool sema_expr_analyse_ct_identifier(SemaContext *context, Expr *expr);

static inline bool sema_expr_analyse_ternary(SemaContext *context, Type *infer_type, Expr *expr);
static inline bool sema_expr_analyse_cast(SemaContext *context, Expr *expr, bool *invalid_cast_ref);
static inline bool sema_expr_analyse_or_error(SemaContext *context, Expr *expr, Expr *left, Expr *right, Type *infer_type, bool *failed_ref);
static inline bool sema_expr_analyse_unary(SemaContext *context, Expr *expr, bool *failed_ref);
static inline bool sema_expr_analyse_embed(SemaContext *context, Expr *expr, bool allow_fail);

static inline bool sema_expr_analyse_rethrow(SemaContext *context, Expr *expr, Type *to);
static inline bool sema_expr_analyse_force_unwrap(SemaContext *context, Expr *expr);
static inline bool sema_expr_analyse_typeid(SemaContext *context, Expr *expr);
static inline bool sema_expr_analyse_call(SemaContext *context, Expr *expr, bool *no_match_ref);

static inline bool sema_expr_analyse_optional(SemaContext *context, Expr *expr, bool *failed_ref);
static inline bool sema_expr_analyse_compiler_const(SemaContext *context, Expr *expr, bool report_missing);
static inline bool sema_expr_analyse_ct_arg(SemaContext *context, Type *infer_type, Expr *expr, bool *no_match_ref);
static inline bool sema_expr_analyse_ct_stringify(SemaContext *context, Expr *expr);
static inline bool sema_expr_analyse_ct_offsetof(SemaContext *context, Expr *expr, bool *failed_ref);
static inline bool sema_expr_analyse_ct_call(SemaContext *context, Expr *expr, bool *failed_ref);
static inline bool sema_analyse_expr_rhs_param(SemaContext *context, Type *to, Expr *expr, bool *no_match_ref);

static inline bool sema_expr_analyse_retval(SemaContext *context, Expr *expr);
static inline bool sema_expr_analyse_expr_list(SemaContext *context, Expr *expr);

// -- binary
static bool sema_expr_analyse_sub(SemaContext *context, Expr *expr, Expr *left, Expr *right, bool *failed_ref);
static bool sema_expr_analyse_add(SemaContext *context, Expr *expr, Expr *left, Expr *right, bool *failed_ref);
static bool sema_expr_analyse_mult(SemaContext *context, Expr *expr, Expr *left, Expr *right, bool *failed_ref);
static bool sema_expr_analyse_div(SemaContext *context, Expr *expr, Expr *left, Expr *right, bool *failed_ref);
static bool sema_expr_analyse_mod(SemaContext *context, Expr *expr, Expr *left, Expr *right, bool *failed_ref);
static bool sema_expr_analyse_bit(SemaContext *context, Expr *expr, Expr *left, Expr *right, OperatorOverload overload, bool *failed_ref);
static bool sema_expr_analyse_enum_add_sub(SemaContext *context, Expr *expr, Expr *left, Expr *right, bool *failed_ref);
static bool sema_expr_analyse_shift(SemaContext *context, Expr *expr, Expr *left, Expr *right, bool *failed_ref);
static bool sema_expr_check_shift_rhs(SemaContext *context, Expr *expr, Expr *left, Type *left_type_flat, Expr *right, Type *right_type_flat, bool *failed_ref,bool
                                      is_assign);
static bool sema_expr_analyse_and_or(SemaContext *context, Expr *expr, Expr *left, Expr *right, bool *failed_ref);
static bool sema_expr_analyse_slice_assign(SemaContext *context, Expr *expr, Type *left_type, Expr *right, bool *failed_ref);
static bool sema_expr_analyse_ct_identifier_assign(SemaContext *context, Expr *expr, Expr *left, Expr *right, bool *failed_ref);
static bool sema_expr_analyse_assign(SemaContext *context, Expr *expr, Expr *left, Expr *right, bool *failed_ref);
static bool sema_expr_analyse_comp(SemaContext *context, Expr *expr, Expr *left, Expr *right, bool *failed_ref);
static bool sema_expr_analyse_op_assign(SemaContext *context, Expr *expr, Expr *left, Expr *right, BinaryOp operator);

// -- unary
static inline bool sema_expr_analyse_addr(SemaContext *context, Expr *expr, bool *failed_ref);
static inline bool sema_expr_analyse_neg_plus(SemaContext *context, Expr *expr);
static inline bool sema_expr_analyse_bit_not(SemaContext *context, Expr *expr, bool *failed_ref);
static inline bool sema_expr_analyse_not(SemaContext *context, Expr *expr);
static inline bool sema_expr_analyse_deref(SemaContext *context, Expr *expr, bool *failed_ref);
static inline bool sema_expr_analyse_ct_incdec(SemaContext *context, Expr *expr, Expr *inner);
static inline bool sema_expr_analyse_incdec(SemaContext *context, Expr *expr);
static inline bool sema_expr_analyse_taddr(SemaContext *context, Expr *expr, bool *failed_ref);

// -- ct_call
static inline bool sema_expr_analyse_ct_alignof(SemaContext *context, Expr *expr, bool *failed_ref);
static inline bool sema_expr_analyse_ct_nameof(SemaContext *context, Expr *expr, bool *failed_ref);
static inline bool sema_expr_analyse_ct_defined(SemaContext *context, Expr *expr);

// -- returns
static inline Type *context_unify_returns(SemaContext *context);

// -- addr helpers
static const char *sema_addr_check_may_take(Expr *inner);
static inline const char *sema_addr_may_take_of_var(Expr *expr, Decl *decl);
static inline const char *sema_addr_may_take_of_ident(Expr *inner);

// -- subscript helpers
static bool sema_subscript_rewrite_index_const_list(Expr *const_list, ArraySize index, bool from_back, Expr *result);
static Type *sema_subscript_find_indexable_type_recursively(Type **type, Expr **parent);
static bool sema_analyse_assign_mutate_overloaded_subscript(SemaContext *context, Expr *main, Expr *subscript_expr, Type *type);

// -- binary helper functions
static void expr_binary_unify_failability(Expr *expr, Expr *left, Expr *right);

static inline bool sema_binary_analyse_subexpr(SemaContext *context, Expr *left, Expr *right);
static inline bool sema_binary_analyse_arithmetic_subexpr(SemaContext *context, Expr *expr, const char *error,
                                                          bool bool_and_bitstruct_is_allowed, OperatorOverload *operator_overload_ref, bool *failed_ref);
static bool sema_binary_check_unclear_op_precedence(Expr *left_side, Expr * main_expr, Expr *right_side);
static bool sema_binary_analyse_ct_op_assign(SemaContext *context, Expr *expr, Expr *left);
static bool sema_binary_arithmetic_promotion(SemaContext *context, Expr *left, Expr *right, Type *left_type, Type *right_type,
								 Expr *parent, const char *error_message, bool allow_bool_vec,
								 OperatorOverload *operator_overload_ref, bool *failed_ref);
static bool sema_binary_is_unsigned_always_same_comparison(SemaContext *context, Expr *expr, Expr *left, Expr *right,
														   Type *lhs_type, Type *rhs_type);
static bool sema_binary_is_expr_lvalue(SemaContext *context, Expr *top_expr, Expr *expr, bool *failed_ref);
static void sema_binary_unify_voidptr(Expr *left, Expr *right, Type **left_type_ref, Type **right_type_ref);

// -- function helper functions
static inline bool sema_expr_analyse_var_call(SemaContext *context, Expr *expr, Expr *var,
                                              Type *func_ptr_type, bool optional,bool *no_match_ref);
static inline bool sema_expr_analyse_func_call(SemaContext *context, Expr *expr, Decl *decl,
											   Expr *struct_var, bool optional, bool *no_match_ref);

static inline bool sema_expr_setup_call_analysis(SemaContext *context, CalledDecl *
                                                 callee,
                                                 SemaContext *macro_context, Expr *call_expr, Type *rtype,
                                                 Ast *yield_body,
                                                 Decl **yield_params, Decl **params, BlockExit **block_exit_ref, InliningSpan *span_ref);
static inline bool sema_call_analyse_func_invocation(SemaContext *context, Decl *decl, Type *type, Expr *expr,
													 Expr *struct_var,
													 bool optional, const char *name, bool *no_match_ref);
static inline bool sema_call_check_invalid_body_arguments(SemaContext *context, Expr *call, CalledDecl *callee);
static inline bool sema_call_evaluate_arguments(SemaContext *context, CalledDecl *callee, Expr *call, bool *optional, bool *no_match_ref);
static inline bool sema_call_check_contract_param_match(SemaContext *context, Decl *param, Expr *expr);
static bool sema_call_analyse_body_expansion(SemaContext *macro_context, Expr *call);
static bool sema_slice_index_is_in_range(SemaContext *context, Type *type, Expr *index_expr, bool end_index, bool from_end, bool *remove_from_end, bool *missing_ref);
static Expr **sema_vasplat_insert(SemaContext *context, Expr **init_expressions, Expr *expr, unsigned insert_point);
static inline bool sema_analyse_expr_dispatch(SemaContext *context, Expr *expr);
static Decl *sema_expr_analyse_var_path(SemaContext *context, Expr *expr, bool *failed_ref);
static inline bool sema_expr_analyse_decl_element(SemaContext *context, DesignatorElement *element, Type *type, Decl **member_ref, ArraySize *index_ref, Type **return_type, unsigned i, SourceSpan loc, bool *is_missing);
static Type *sema_expr_check_type_exists(SemaContext *context, TypeInfo *type_info);
static inline bool sema_cast_ct_ident_rvalue(SemaContext *context, Expr *expr);
static bool sema_expr_rewrite_to_typeid_property(SemaContext *context, Expr *expr, Expr *typeid, const char *kw, bool *was_error);
static bool sema_expr_rewrite_to_type_property(SemaContext *context, Expr *expr, Type *type, TypeProperty property, Type *parent_type);
static bool sema_type_property_is_valid_for_type(CanonicalType *original_type, TypeProperty property);
static bool sema_expr_rewrite_typeid_call(Expr *expr, Expr *typeid, TypeIdInfoKind kind, Type *result_type);
static inline void sema_expr_rewrite_typeid_kind(Expr *expr, Expr *parent);
static inline bool sema_expr_replace_with_enum_array(SemaContext *context, Expr *enum_array_expr, Decl *enum_decl);
static inline bool sema_expr_replace_with_enum_name_array(SemaContext *context, Expr *enum_array_expr, Decl *enum_decl);
static inline void sema_expr_rewrite_to_type_nameof(Expr *expr, Type *type, TokenType name_type);

static inline bool sema_create_const_kind(SemaContext *context, Expr *expr, Type *type);
static inline bool sema_create_const_len(Expr *expr, Type *type, Type *flat);
static inline bool sema_create_const_inner(SemaContext *context, Expr *expr, Type *type);
static inline bool sema_create_const_min(Expr *expr, Type *type, Type *flat);
static inline bool sema_create_const_max(Expr *expr, Type *type, Type *flat);
static inline bool sema_create_const_params(Expr *expr, Type *type);
static inline void sema_create_const_membersof(Expr *expr, Type *type, AlignSize alignment, AlignSize offset);
static inline void sema_create_const_methodsof(Expr *expr, Type *type);

static inline bool expr_both_any_integer_or_integer_bool_vector(Expr *left, Expr *right);
static inline bool expr_both_const_foldable(Expr *left, Expr *right, BinaryOp op);
static inline bool expr_const_foldable_unary(Expr *expr, UnaryOp unary);
static inline bool sema_identifier_find_possible_inferred(SemaContext *context, Type *to, Expr *expr);
static inline bool sema_expr_analyse_enum_constant(SemaContext *context, Expr *expr, const char *name, Decl *decl);

static inline bool sema_cast_ident_rvalue(SemaContext *context, Expr *expr);
static inline bool sema_cast_rvalue(SemaContext *context, Expr *expr, bool mutate);

static inline bool sema_expr_analyse_type_access(SemaContext *context, Expr *expr, Type *parent_type, Expr *identifier, bool *missing_ref);
static inline bool sema_expr_analyse_member_access(SemaContext *context, Expr *expr, Expr *parent, Expr *identifier, bool *missing_ref);
static inline bool sema_expr_fold_to_member(Expr *expr, Expr *parent, Decl *member);
static inline bool sema_expr_fold_to_index(Expr *expr, Expr *parent, SubscriptIndex index_expr);
static inline bool sema_expr_fold_hash(SemaContext *context, Expr *expr);

static inline void sema_expr_flatten_const_ident(Expr *expr);

static inline Expr **sema_prepare_splat_insert(Expr **exprs, unsigned added, unsigned insert_point);
static inline bool sema_analyse_maybe_dead_expr(SemaContext *, Expr *expr, bool is_dead, Type *infer_type);
static inline bool sema_insert_binary_overload(SemaContext *context, Expr *expr, Decl *overload, Expr *lhs, Expr *rhs, bool reverse);
static bool sema_replace_with_overload(SemaContext *context, Expr *expr, Expr *left, Expr *right, Type *left_type, OperatorOverload* operator_overload_ref);

INLINE bool sema_expr_analyse_ptr_add(SemaContext *context, Expr *expr, Expr *left, Expr *right, CanonicalType *left_type, CanonicalType *right_type, Type *cast_to_iptr, bool *failed_ref);
static Type *defer_iptr_cast(Expr *maybe_pointer);

// -- implementations

typedef struct
{
	bool in_no_eval;
	InliningSpan *old_inlining;
} ContextSwitchState;

static inline ContextSwitchState context_switch_state_push(SemaContext *context, SemaContext *new_context)
{

	ContextSwitchState state = { .in_no_eval = new_context->call_env.in_no_eval, .old_inlining = new_context->inlined_at };
	new_context->call_env.in_no_eval = context->call_env.in_no_eval;
	new_context->inlined_at = context->inlined_at;
	return state;
}

static inline void context_switch_stat_pop(SemaContext *swapped, ContextSwitchState state)
{
	swapped->call_env.in_no_eval = state.in_no_eval;
	swapped->inlined_at = state.old_inlining;
}

Expr *sema_enter_inline_member(Expr *parent, CanonicalType *type)
{
	Expr *expr;
	switch (type->type_kind)
	{
		case TYPE_STRUCT:
		{
			Decl *decl = type->decl;
			if (!decl->is_substruct) return NULL;
			expr = expr_access_inline_member(parent, decl);
			break;
		}
		case TYPE_TYPEDEF:
		{
			Decl *decl = type->decl;
			if (!decl->is_substruct) return NULL;
			expr = expr_copy(parent);
			type = type->decl->distinct->type;
			expr->type = type;
			break;
		}
		case TYPE_ENUM:
		{
			Decl *decl = type->decl;
			if (!decl->is_substruct) return NULL;

			if (parent->expr_kind == EXPR_CONST)
			{
				if (decl->enums.inline_value)
				{
					expr = expr_new_expr(EXPR_CONST, parent);
					expr_rewrite_const_int(expr, decl->enums.type_info->type, parent->const_expr.enum_val->enum_constant.inner_ordinal);
					return expr;
				}
				expr = copy_expr_single(parent->const_expr.enum_val->enum_constant.associated[decl->enums.inline_index]);
				break;
			}
			if (decl->enums.inline_value)
			{
				expr = copy_expr_single(parent);
				expr->type = type_add_optional(decl->enums.type_info->type, IS_OPTIONAL(parent));
				break;
			}
			expr = expr_new(EXPR_ACCESS_RESOLVED, parent->span);
			expr->resolve_status = RESOLVE_DONE;
			expr->access_resolved_expr.parent = parent;
			expr->access_resolved_expr.ref = decl->enums.parameters[decl->enums.inline_value];
			expr->type = expr->access_resolved_expr.ref->type;
			break;
		}
		default:
			return NULL;
	}
	if (IS_OPTIONAL(parent)) expr->type = type_add_optional(expr->type, true);
	return expr;
}

Expr *sema_expr_analyse_ct_arg_index(SemaContext *context, Expr *index_expr, unsigned *index_ref)
{
	unsigned args = vec_size(context->macro_varargs);
	if (!sema_analyse_expr_rvalue(context, index_expr)) return poisoned_expr;
	if (!type_is_integer(index_expr->type))
	{
		SEMA_ERROR(index_expr, "Expected the argument index here, but found a value of type %s.", type_quoted_error_string(index_expr->type));
		return poisoned_expr;
	}
	if (!sema_cast_const(index_expr))
	{
		SEMA_ERROR(index_expr, "Vararg functions need a constant argument, but this is a runtime value.");
		return poisoned_expr;
	}
	Int index_val = index_expr->const_expr.ixx;
	if (int_is_neg(index_val))
	{
		SEMA_ERROR(index_expr, "The index cannot be negative.");
		return poisoned_expr;
	}
	Int int_max = { .i = { .low = args }, .type = TYPE_U32 };
	if (int_comp(index_val, int_max, BINARYOP_GE))
	{
		SEMA_ERROR(index_expr, "Only %u vararg%s exist.", args, args == 1 ? "" : "s");
		return poisoned_expr;
	}
	if (index_ref) *index_ref = (unsigned)index_val.i.low;
	return context->macro_varargs[(size_t)index_val.i.low];
}

Expr *sema_resolve_string_ident(SemaContext *context, Expr *inner, bool report_missing)
{
	ASSERT_SPAN(inner, expr_is_const_string(inner));
	Path *path = NULL;
	const char *interned_version = NULL;
	TokenType token = sema_splitpathref(inner->const_expr.bytes.ptr, inner->const_expr.bytes.len, &path, &interned_version);
	switch (token)
	{
		case TOKEN_CONST_IDENT:
			inner->unresolved_ident_expr.is_const = true;
			break;
		case TOKEN_HASH_IDENT:
			if (path) goto NO_PATH;
			inner->expr_kind = EXPR_HASH_IDENT;
			inner->ct_ident_expr.identifier = interned_version;
			inner->resolve_status = RESOLVE_NOT_DONE;
			return inner;
		case TOKEN_CT_IDENT:
			if (path) goto NO_PATH;
			inner->expr_kind = EXPR_CT_IDENT;
			inner->ct_ident_expr.identifier = interned_version;
			inner->resolve_status = RESOLVE_NOT_DONE;
			return inner;
		case TOKEN_AT_IDENT:
		case TOKEN_IDENT:
			if (!interned_version)
			{
				if (report_missing)
				{
					SEMA_ERROR(inner, "'%.*s' could not be found, did you spell it right?", (int)inner->const_expr.bytes.len, inner->const_expr.bytes.ptr);
				}
				return NULL;
			}
			inner->unresolved_ident_expr.is_const = false;
			break;
		case TYPE_TOKENS:
		{
			if (path) goto NO_PATH;
			TypeInfo *info = type_info_new_base(type_from_token(token), inner->span);
			inner->expr_kind = EXPR_TYPEINFO;
			inner->resolve_status = RESOLVE_NOT_DONE;
			inner->type_expr = info;
			return inner;
		}
		case TOKEN_CT_TYPE_IDENT:
			if (path) goto NO_PATH;
			FALLTHROUGH;
		case TOKEN_TYPE_IDENT:
		{
			TypeInfo *info = type_info_new(TYPE_INFO_IDENTIFIER, inner->span);
			info->unresolved.name = interned_version;
			info->unresolved.path = path;
			info->resolve_status = RESOLVE_NOT_DONE;
			inner->expr_kind = EXPR_TYPEINFO;
			inner->resolve_status = RESOLVE_NOT_DONE;
			inner->type_expr = info;
			return inner;
		}
		default:
			SEMA_ERROR(inner, "'%.*s' could not be resolved to a valid symbol.", (int)inner->const_expr.bytes.len, inner->const_expr.bytes.ptr);
			return poisoned_expr;
	}
	inner->expr_kind = EXPR_UNRESOLVED_IDENTIFIER;
	inner->resolve_status = RESOLVE_NOT_DONE;
	inner->unresolved_ident_expr.ident = interned_version;
	inner->unresolved_ident_expr.path = path;
	return inner;
NO_PATH:
	if (report_missing)
	{
		SEMA_ERROR(inner, "Unexpected path in '%.*s'.", (int)inner->const_expr.bytes.len, inner->const_expr.bytes.ptr);
	}
	return NULL;
}

Expr *sema_ct_eval_expr(SemaContext *context, bool is_type_eval, Expr *inner, bool report_missing)
{
	if (!sema_analyse_ct_expr(context, inner)) return NULL;
	if (!expr_is_const_string(inner))
	{
		SEMA_ERROR(inner, "'%s' expects a constant string as the argument.", is_type_eval ? "$evaltype" : "$eval");
		return poisoned_expr;
	}
	return sema_resolve_string_ident(context, inner, report_missing);
}

void expr_set_to_ref(Expr *expr)
{
	switch (expr->expr_kind)
	{
		case EXPR_ACCESS_UNRESOLVED:
			expr->access_unresolved_expr.is_ref = true;
			break;
		case EXPR_SUBSCRIPT:
			expr->subscript_expr.ref = true;
			break;
		case EXPR_HASH_IDENT:
			expr->hash_ident_expr.is_ref = true;
			break;
		case EXPR_OTHER_CONTEXT:
			expr->expr_other_context.is_ref = true;
			break;
		default:
			break;
	}
}

Expr *expr_access_inline_member(Expr *parent, Decl *parent_decl)
{
	Expr *embedded_struct = expr_new(EXPR_ACCESS_RESOLVED, parent->span);
	embedded_struct->resolve_status = RESOLVE_DONE;
	embedded_struct->access_resolved_expr.parent = parent;
	embedded_struct->access_resolved_expr.ref = parent_decl->strukt.members[0];
	embedded_struct->type = type_add_optional(embedded_struct->access_resolved_expr.ref->type, IS_OPTIONAL(parent));
	return embedded_struct;
}

static inline bool expr_const_foldable_unary(Expr *expr, UnaryOp unary)
{
	if (!sema_cast_const(expr) || !expr_is_const(expr)) return false;
	ConstKind kind = expr->const_expr.const_kind;
	switch (unary)
	{
		case UNARYOP_INC:
		case UNARYOP_DEC:
			return false;
		case UNARYOP_PLUS:
		case UNARYOP_NEG:
			return kind == CONST_FLOAT || kind == CONST_INTEGER;
		case UNARYOP_BITNEG:
			return kind == CONST_BOOL || kind == CONST_INTEGER;
		case UNARYOP_NOT:
			return kind == CONST_BOOL;
		case UNARYOP_ERROR:
			UNREACHABLE
		case UNARYOP_DEREF:
		case UNARYOP_ADDR:
		case UNARYOP_TADDR:
			UNREACHABLE
	}
	UNREACHABLE
}
static inline bool expr_both_const_foldable(Expr *left, Expr *right, BinaryOp op)
{
	if (!sema_cast_const(left) || !sema_cast_const(right) || !expr_is_const(left) || !expr_is_const(right)) return false;
	ConstKind a = left->const_expr.const_kind;
	ConstKind b = right->const_expr.const_kind;
	switch (a)
	{
		case CONST_BOOL:
			if (a != b) return false;
			switch (op)
			{
				case BINARYOP_BIT_AND:
				case BINARYOP_BIT_OR:
				case BINARYOP_BIT_XOR:
				case BINARYOP_NE:
				case BINARYOP_EQ:
				case BINARYOP_AND:
				case BINARYOP_OR:
					return true;
				default:
					return false;
			}
		case CONST_FLOAT:
		case CONST_INTEGER:
			if (a != b) return false;
			return op != BINARYOP_AND && op != BINARYOP_OR;
		case CONST_BYTES:
		case CONST_STRING:
			if (op != BINARYOP_EQ && op != BINARYOP_NE) return false;
			return a == b || b == CONST_SLICE || b == CONST_BYTES || b == CONST_STRING;
		case CONST_ENUM:
		case CONST_FAULT:
		case CONST_TYPEID:
		case CONST_REF:
			break;
		case CONST_POINTER:
			if (op == BINARYOP_SUB) return true;
			break;
		case CONST_SLICE:
			if (op != BINARYOP_EQ && op != BINARYOP_NE) return false;
			return b == CONST_BYTES || b == CONST_STRING;
		case CONST_INITIALIZER:
			switch (type_flatten(left->type)->type_kind)
			{
				case TYPE_VECTOR:
					if (a != b) return false;
					return op == BINARYOP_EQ || op == BINARYOP_NE;
				case TYPE_BITSTRUCT:
					if (a != b) return false;
					switch (op)
					{
						case BINARYOP_EQ:
						case BINARYOP_NE:
						case BINARYOP_BIT_AND:
						case BINARYOP_BIT_XOR:
						case BINARYOP_BIT_OR:
							return true;
						default:
							return false;
					}
				default:
					return false;
			}
		case CONST_UNTYPED_LIST:
			return false;
		case CONST_MEMBER:
			return true;
	}
	return (a == b) && (op == BINARYOP_EQ || op == BINARYOP_NE);
}

static inline bool expr_both_any_integer_or_integer_bool_vector(Expr *left, Expr *right)
{
	Type *flatten_left = type_flatten(left->type);
	Type *flatten_right = type_flatten(right->type);
	if (type_is_integer(flatten_left) && type_is_integer(flatten_right)) return true;

	if (flatten_left->type_kind != TYPE_VECTOR || flatten_right->type_kind != TYPE_VECTOR) return false;

	return type_is_integer_or_bool_kind(flatten_left->array.base) && type_is_integer_or_bool_kind(flatten_right->array.base);
}

static void expr_binary_unify_failability(Expr *expr, Expr *left, Expr *right)
{
	expr->type = type_add_optional(left->type, IS_OPTIONAL(right));
}

CondResult sema_check_comp_time_bool(SemaContext *context, Expr *expr)
{
	CondResult result = COND_MISSING;
	if (!sema_analyse_cond_expr(context, expr, &result)) return COND_MISSING;
	if (result == COND_MISSING)
	{
		SEMA_ERROR(expr, "Compile time evaluation requires a compile time constant value.");
	}
	return result;
}

bool sema_expr_analyse_sprintf(SemaContext *context, Expr *expr, Expr *format_string, Expr **args, unsigned num_args)
{
	if (!sema_analyse_expr_rvalue(context, format_string)) return false;
	if (!sema_cast_const(format_string))
	{
		RETURN_SEMA_ERROR(format_string, "Expected a constant format string expression.");
	}
	for (unsigned i = 0; i < num_args; i++)
	{
		Expr *e = args[i];
		if (!sema_analyse_expr_rvalue(context, e)) return false;
		if (!sema_cast_const(e))
		{
			RETURN_SEMA_ERROR(e, "Expected a constant expression.");
		}
	}
	if (!expr_is_const_string(format_string))
	{
		RETURN_SEMA_ERROR(format_string, "Expected a constant format string.");
	}
	const char *inner_str = format_string->const_expr.bytes.ptr;
	ArraySize len = format_string->const_expr.bytes.len;
	scratch_buffer_clear();
	ArrayIndex current_index = 0;
	for (ArraySize i = 0; i < len; i++)
	{
		char c = inner_str[i];
		if (c == '%')
		{
			i++;
			switch (inner_str[i])
			{
				case 's':
					if (current_index == num_args) RETURN_SEMA_ERROR(format_string, "Too many arguments in format string.");
					expr_const_to_scratch_buffer(&(args[current_index++]->const_expr));
					continue;
				case '%':
					scratch_buffer_append_char('%');
					continue;
				default:
					RETURN_SEMA_ERROR(format_string, "Only '%%s' is supported for compile time sprintf.");
			}
		}
		scratch_buffer_append_char(c);
	}
	if (current_index != num_args)
	{
		RETURN_SEMA_ERROR(expr, "Too many arguments to sprintf.");
	}
	expr_rewrite_const_string(expr, scratch_buffer_copy());
	return true;
}


static bool sema_binary_is_expr_lvalue(SemaContext *context, Expr *top_expr, Expr *expr, bool *failed_ref)
{
	if (expr->expr_kind == EXPR_CT_SUBSCRIPT) return true;
	switch (expr->expr_kind)
	{
		case UNRESOLVED_EXPRS:
			UNREACHABLE
		case EXPR_SWIZZLE:
			if (failed_ref) goto FAILED_REF;
			if (expr->swizzle_expr.is_overlapping)
			{
				RETURN_SEMA_ERROR(expr, "You cannot use swizzling to assign to multiple elements if they are overlapping");
			}
			return sema_binary_is_expr_lvalue(context, top_expr, exprptr(expr->swizzle_expr.parent), failed_ref);
		case EXPR_LAMBDA:
			if (failed_ref) goto FAILED_REF;
			RETURN_SEMA_ERROR(expr, "This expression is a value and cannot be assigned to.");
		case EXPR_CT_IDENT:
			return true;
		case EXPR_EXT_TRUNC:
		case EXPR_INT_TO_BOOL:
		case EXPR_DISCARD:
			goto ERR;
		case EXPR_IDENTIFIER:
		{
			Decl *decl = expr->ident_expr;
			if (decl->decl_kind != DECL_VAR)
			{
				if (failed_ref) goto FAILED_REF;
				RETURN_SEMA_ERROR(top_expr, "You cannot assign a value to %s.", decl_to_a_name(decl));
			}
			if (decl->var.kind == VARDECL_CONST)
			{
				if (failed_ref) goto FAILED_REF;
				RETURN_SEMA_ERROR(top_expr, "You cannot assign to a constant.");
			}
			decl = decl_raw(decl);
			switch (decl->var.kind)
			{
				case VARDECL_LOCAL_CT:
				case VARDECL_LOCAL_CT_TYPE:
				case VARDECL_LOCAL:
				case VARDECL_GLOBAL:
				case VARDECL_PARAM:
				case VARDECL_PARAM_CT:
				case VARDECL_PARAM_CT_TYPE:
					return true;
				case VARDECL_CONST:
				case VARDECL_PARAM_EXPR:
					UNREACHABLE
				case VARDECL_MEMBER:
				case VARDECL_BITMEMBER:
					goto ERR;
				case VARDECL_UNWRAPPED:
				case VARDECL_ERASE:
				case VARDECL_REWRAPPED:
					UNREACHABLE
			}
			UNREACHABLE
		}
		case EXPR_UNARY:
			if (expr->unary_expr.operator != UNARYOP_DEREF) goto ERR;
			if (IS_OPTIONAL(expr))
			{
				if (failed_ref) goto FAILED_REF;
				RETURN_SEMA_ERROR(top_expr, "You cannot assign to a dereferenced optional.");
			}
			return true;
		case EXPR_BITACCESS:
		case EXPR_ACCESS_RESOLVED:
			if (!sema_binary_is_expr_lvalue(context, top_expr, expr->access_resolved_expr.parent, failed_ref)) return false;
			goto CHECK_OPTIONAL;
		case EXPR_SUBSCRIPT:
		case EXPR_SLICE:
		case EXPR_SUBSCRIPT_ASSIGN:
			goto CHECK_OPTIONAL;
		case EXPR_CONST:
			if (failed_ref) goto FAILED_REF;
			RETURN_SEMA_ERROR(top_expr, "You cannot assign to a constant expression.");
		case EXPR_CT_ARG:
		case EXPR_HASH_IDENT:
		case EXPR_POISONED:
		case EXPR_ADDR_CONVERSION:
		case EXPR_ASM:
		case EXPR_BENCHMARK_HOOK:
		case EXPR_BINARY:
		case EXPR_BITASSIGN:
		case EXPR_BUILTIN:
		case EXPR_BUILTIN_ACCESS:
		case EXPR_CALL:
		case EXPR_CATCH:
		case EXPR_COMPILER_CONST:
		case EXPR_COND:
		case EXPR_CT_CALL:
		case EXPR_CT_ASSIGNABLE:
		case EXPR_CT_DEFINED:
		case EXPR_CT_EVAL:
		case EXPR_CT_IS_CONST:
		case EXPR_DECL:
		case EXPR_DEFAULT_ARG:
		case EXPR_DESIGNATED_INITIALIZER_LIST:
		case EXPR_DESIGNATOR:
		case EXPR_FORCE_UNWRAP:
		case EXPR_INITIALIZER_LIST:
		case EXPR_LAST_FAULT:
		case EXPR_MACRO_BLOCK:
		case EXPR_MACRO_BODY_EXPANSION:
		case EXPR_MAKE_ANY:
		case EXPR_MAKE_SLICE:
		case EXPR_MEMBER_GET:
		case EXPR_MEMBER_SET:
		case EXPR_NAMED_ARGUMENT:
		case EXPR_NOP:
		case EXPR_OPERATOR_CHARS:
		case EXPR_OPTIONAL:
		case EXPR_POINTER_OFFSET:
		case EXPR_POST_UNARY:
		case EXPR_PTR_ACCESS:
		case EXPR_ENUM_FROM_ORD:
		case EXPR_SLICE_LEN:
		case EXPR_FLOAT_TO_INT:
		case EXPR_INT_TO_FLOAT:
		case EXPR_INT_TO_PTR:
		case EXPR_PTR_TO_INT:
		case EXPR_RECAST:
		case EXPR_RETHROW:
		case EXPR_RETVAL:
		case EXPR_RVALUE:
		case EXPR_SLICE_ASSIGN:
		case EXPR_SLICE_COPY:
		case EXPR_SPLAT:
		case EXPR_STRINGIFY:
		case EXPR_TERNARY:
		case EXPR_TEST_HOOK:
		case EXPR_TRY:
		case EXPR_TRY_UNWRAP_CHAIN:
		case EXPR_TYPECALL:
		case EXPR_TYPEID_INFO:
		case EXPR_TYPEINFO:
		case EXPR_VECTOR_FROM_ARRAY:
		case EXPR_VECTOR_TO_ARRAY:
		case EXPR_SLICE_TO_VEC_ARRAY:
		case EXPR_SCALAR_TO_VECTOR:
		case EXPR_SUBSCRIPT_ADDR:
		case EXPR_EXPRESSION_LIST:
		case EXPR_TWO:
			goto ERR;
	}
	UNREACHABLE
ERR:
	if (failed_ref) goto FAILED_REF;
	RETURN_SEMA_ERROR(top_expr, "An assignable expression, like a variable, was expected here.");
CHECK_OPTIONAL:
	if (IS_OPTIONAL(expr))
	{
		if (failed_ref) goto FAILED_REF;
		RETURN_SEMA_ERROR(top_expr, "You cannot assign to an optional value.");
	}
	return true;
FAILED_REF:
	*failed_ref = true;
	return false;
}

static bool expr_may_ref(Expr *expr)
{
	switch (expr->expr_kind)
	{
		case EXPR_SWIZZLE:
		case EXPR_LAMBDA:
		case EXPR_CT_IDENT:
		case EXPR_DEFAULT_ARG:
		case EXPR_TYPECALL:
		case EXPR_MEMBER_GET:
		case EXPR_MEMBER_SET:
		case EXPR_EXT_TRUNC:
		case EXPR_PTR_ACCESS:
		case EXPR_VECTOR_TO_ARRAY:
		case EXPR_SLICE_TO_VEC_ARRAY:
		case EXPR_SCALAR_TO_VECTOR:
		case EXPR_ENUM_FROM_ORD:
		case EXPR_FLOAT_TO_INT:
		case EXPR_INT_TO_FLOAT:
		case EXPR_INT_TO_PTR:
		case EXPR_PTR_TO_INT:
		case EXPR_SLICE_LEN:
		case EXPR_VECTOR_FROM_ARRAY:
		case EXPR_INT_TO_BOOL:
		case EXPR_RVALUE:
		case EXPR_RECAST:
		case EXPR_DISCARD:
		case EXPR_ADDR_CONVERSION:
		case EXPR_MAKE_SLICE:
			return false;
		case EXPR_SUBSCRIPT_ASSIGN:
			return true;
		case UNRESOLVED_EXPRS:
			UNREACHABLE
		case EXPR_IDENTIFIER:
		{
			Decl *decl = expr->ident_expr;
			if (decl->decl_kind != DECL_VAR) return false;
			if (decl->var.kind == VARDECL_CONST) return false;
			decl = decl_raw(decl);
			switch (decl->var.kind)
			{
				case VARDECL_LOCAL_CT:
				case VARDECL_LOCAL_CT_TYPE:
				case VARDECL_LOCAL:
				case VARDECL_GLOBAL:
				case VARDECL_PARAM:
				case VARDECL_PARAM_CT:
				case VARDECL_PARAM_CT_TYPE:
					return true;
				case VARDECL_CONST:
				case VARDECL_PARAM_EXPR:
				case VARDECL_MEMBER:
				case VARDECL_BITMEMBER:
					return false;
				case VARDECL_UNWRAPPED:
				case VARDECL_ERASE:
				case VARDECL_REWRAPPED:
					UNREACHABLE
			}
			UNREACHABLE
		}
		case EXPR_UNARY:
			return expr->unary_expr.operator == UNARYOP_DEREF;
		case EXPR_BITACCESS:
		case EXPR_ACCESS_RESOLVED:
			return expr_may_ref(expr->access_resolved_expr.parent);
		case EXPR_SUBSCRIPT:
		case EXPR_SLICE:
		case EXPR_SUBSCRIPT_ADDR:
			return true;
		case EXPR_HASH_IDENT:
			return false;
		case EXPR_TWO:
			return expr_may_ref(expr->two_expr.last);
		case EXPR_EXPRESSION_LIST:
			if (!vec_size(expr->expression_list)) return false;
			return expr_may_ref(VECLAST(expr->expression_list));
		case EXPR_ASM:
		case EXPR_BENCHMARK_HOOK:
		case EXPR_BINARY:
		case EXPR_BITASSIGN:
		case EXPR_BUILTIN:
		case EXPR_BUILTIN_ACCESS:
		case EXPR_CALL:
		case EXPR_CATCH:
		case EXPR_COMPILER_CONST:
		case EXPR_COND:
		case EXPR_CONST:
		case EXPR_CT_ARG:
		case EXPR_CT_CALL:
		case EXPR_CT_ASSIGNABLE:
		case EXPR_CT_DEFINED:
		case EXPR_CT_EVAL:
		case EXPR_CT_IS_CONST:
		case EXPR_DECL:
		case EXPR_DESIGNATED_INITIALIZER_LIST:
		case EXPR_DESIGNATOR:
		case EXPR_FORCE_UNWRAP:
		case EXPR_INITIALIZER_LIST:
		case EXPR_LAST_FAULT:
		case EXPR_MACRO_BLOCK:
		case EXPR_MACRO_BODY_EXPANSION:
		case EXPR_NAMED_ARGUMENT:
		case EXPR_NOP:
		case EXPR_OPERATOR_CHARS:
		case EXPR_OPTIONAL:
		case EXPR_POINTER_OFFSET:
		case EXPR_POISONED:
		case EXPR_POST_UNARY:
		case EXPR_RETHROW:
		case EXPR_RETVAL:
		case EXPR_SLICE_ASSIGN:
		case EXPR_SLICE_COPY:
		case EXPR_SPLAT:
		case EXPR_STRINGIFY:
		case EXPR_TERNARY:
		case EXPR_TEST_HOOK:
		case EXPR_TRY:
		case EXPR_TRY_UNWRAP_CHAIN:
		case EXPR_TYPEID_INFO:
		case EXPR_TYPEINFO:
		case EXPR_MAKE_ANY:
			return false;
	}
	UNREACHABLE
}

bool sema_expr_check_assign(SemaContext *context, Expr *expr, bool *failed_ref)
{
	Expr *inner;
	if (!sema_binary_is_expr_lvalue(context, expr, expr, failed_ref)) return false;
	if (expr->expr_kind == EXPR_SUBSCRIPT)
	{
		inner = exprptr(expr->subscript_expr.expr);
		if (inner->expr_kind == EXPR_IDENTIFIER) inner->ident_expr->var.is_written = true;
		goto CHECK_INNER;
	}
	if (expr->expr_kind == EXPR_BITACCESS || expr->expr_kind == EXPR_ACCESS_RESOLVED) expr = expr->access_resolved_expr.parent;
	if (expr->expr_kind == EXPR_IDENTIFIER)
	{
		expr->ident_expr->var.is_written = true;
	}
	if (expr->expr_kind != EXPR_UNARY) return true;
	inner = expr->inner_expr;
CHECK_INNER:
	if (inner->expr_kind != EXPR_IDENTIFIER) return true;
	Decl *decl = inner->ident_expr;
	if (decl->decl_kind != DECL_VAR) return true;
	if (!decl->var.in_param || decl->var.out_param) return true;
	RETURN_SEMA_ERROR(inner, "'in' parameters may not be assigned to.");
}

static inline bool sema_cast_ident_rvalue(SemaContext *context, Expr *expr)
{
	Decl *decl = expr->ident_expr;
	decl = decl_flatten(decl);
	switch (decl->decl_kind)
	{
		case DECL_FNTYPE:
		case DECL_FUNC:
			SEMA_ERROR(expr, "Expected function followed by (...) or prefixed by &.");
			return expr_poison(expr);
		case DECL_MACRO:
			SEMA_ERROR(expr, "Expected a macro followed by (...).");
			return expr_poison(expr);
		case DECL_ENUM_CONSTANT:
			// This can't happen, inferred identifiers are folded to consts they are never identifiers.
			UNREACHABLE
		case DECL_VAR:
			break;
		case DECL_FAULT:
			expr_rewrite_const_fault(expr, decl);
			return true;
		case DECL_ALIAS:
		case DECL_ALIAS_PATH:
		case DECL_ATTRIBUTE:
		case DECL_BODYPARAM:
		case DECL_CT_ASSERT:
		case DECL_CT_ECHO:
		case DECL_CT_EXEC:
		case DECL_CT_INCLUDE:
		case DECL_DECLARRAY:
		case DECL_TYPEDEF:
		case DECL_ERASED:
		case DECL_GROUP:
		case DECL_IMPORT:
		case DECL_TYPE_ALIAS:
			UNREACHABLE
		case DECL_POISONED:
			return expr_poison(expr);
		case DECL_LABEL:
			SEMA_ERROR(expr, "Did you intend to use the label '%s' here?", decl->name);
			return expr_poison(expr);
		case DECL_BITSTRUCT:
			SEMA_ERROR(expr, "Expected bitstruct followed by (...) or '.'.");
			return expr_poison(expr);
		case DECL_STRUCT:
			SEMA_ERROR(expr, "Expected struct followed by {...} or '.'.");
			return expr_poison(expr);
		case DECL_INTERFACE:
			SEMA_ERROR(expr, "Expected an interface to be followed by '.' when used as an expression.");
			return expr_poison(expr);
		case DECL_UNION:
			SEMA_ERROR(expr, "Expected union followed by {...} or '.'.");
			return expr_poison(expr);
		case DECL_ENUM:
		case DECL_CONST_ENUM:
			SEMA_ERROR(expr, "Expected enum name followed by '.' and an enum value.");
			return expr_poison(expr);
	}
	switch (decl->var.kind)
	{
		case VARDECL_CONST:
		case VARDECL_GLOBAL:
		case VARDECL_LOCAL:
		case VARDECL_LOCAL_CT:
		case VARDECL_LOCAL_CT_TYPE:
			if (decl->in_init)
			{
				RETURN_SEMA_ERROR(expr, "This looks like the initialization of the variable was circular.");
			}
			break;
		default:
			break;
	}
	switch (decl->var.kind)
	{
		case VARDECL_CONST:
			if (decl->is_extern) return true;
			if (type_is_abi_aggregate(decl->type)) return true;
			expr_replace(expr, copy_expr_single(decl->var.init_expr));
			if (!sema_analyse_expr_rvalue(context, expr)) return false;
			if (!sema_cast_const(expr) && !expr_is_runtime_const(expr))
			{
				RETURN_SEMA_ERROR(decl->var.init_expr, "The expression must be constant.");
			}
			return true;
		case VARDECL_PARAM_EXPR:
			UNREACHABLE
		case VARDECL_PARAM_CT_TYPE:
		case VARDECL_PARAM_CT:
		case VARDECL_LOCAL_CT:
		case VARDECL_LOCAL_CT_TYPE:
		case VARDECL_ERASE:
		case VARDECL_REWRAPPED:
			// Impossible to reach this, they are already unfolded
			UNREACHABLE
		case VARDECL_LOCAL:
			if (decl->var.copy_const && decl->var.init_expr && expr_is_const(decl->var.init_expr))
			{
				assert(decl->var.init_expr->resolve_status == RESOLVE_DONE);
				expr_replace(expr, copy_expr_single(decl->var.init_expr));
				return true;
			}
			FALLTHROUGH;
		case VARDECL_PARAM:
		case VARDECL_GLOBAL:
		case VARDECL_UNWRAPPED:
			return true;
		case VARDECL_BITMEMBER:
		case VARDECL_MEMBER:
			SEMA_ERROR(expr, "Expected '%s' followed by a method call or property.", decl->name);
			return expr_poison(expr);
	}
	UNREACHABLE
}

static inline bool sema_expr_analyse_ternary(SemaContext *context, Type *infer_type, Expr *expr)
{
	Expr *left = exprptrzero(expr->ternary_expr.then_expr);
	Expr *cond = exprptr(expr->ternary_expr.cond);
	CondResult path = COND_MISSING;
	bool is_const = expr->ternary_expr.is_const;
	// Normal
	if (left)
	{
		if (!sema_analyse_cond_expr(context, cond, &path)) return expr_poison(expr);
		if (is_const && path == COND_MISSING)
		{
			RETURN_SEMA_ERROR(cond, "When using '\?\?\?' the cond expression must evaluate to a constant.");
		}
		bool is_left = path == COND_TRUE;
		if (!is_const || is_left)
		{
			if (!sema_analyse_maybe_dead_expr(context, left, !is_left, infer_type)) return expr_poison(expr);
		}
	}
	else
	{
		// Elvis
		if (!sema_analyse_expr_rvalue(context, cond)) return expr_poison(expr);
		ASSERT_SPAN(expr, cond && cond->type);
		Type *type = cond->type->canonical;
		if (type->type_kind != TYPE_BOOL && !may_cast(context, cond, type_bool, true, true))
		{
			RETURN_SEMA_ERROR(cond, "Cannot convert expression to boolean.");
		}
		if (expr_is_const(cond))
		{
			Expr *copy = copy_expr_single(cond);
			cast_no_check(copy, type_bool, false);
			ASSERT_SPAN(expr, expr_is_const_bool(copy));
			path = copy->const_expr.b ? COND_TRUE : COND_FALSE;
			expr->ternary_expr.then_expr = exprid(cond);
			expr->ternary_expr.cond = exprid(copy);
			left = cond;
		}
		else
		{
			// From foo :? bar
			// (bool)($temp = foo) ? $temp : bar
			Decl *temp = decl_new_generated_var(cond->type, VARDECL_LOCAL, cond->span);
			temp->var.init_expr = expr_copy(cond);
			cond->expr_kind = EXPR_DECL;
			cond->decl_expr = temp;
			cond->resolve_status = RESOLVE_NOT_DONE;
			if (!sema_analyse_expr_rvalue(context, cond)) return false;
			if (!cast_explicit(context, cond, type_bool)) return false;
			expr->ternary_expr.then_expr = exprid(left = expr_variable(temp));
			if (!sema_analyse_expr_rvalue(context, left)) return false;
		}
	}

	bool is_right = path == COND_FALSE;

	Expr *right = exprptr(expr->ternary_expr.else_expr);
	if (!is_const || is_right)
	{
		if (!sema_analyse_maybe_dead_expr(context, right, !is_right, infer_type)) return expr_poison(expr);
	}

	if (is_const)
	{
		switch (path)
		{
			case COND_TRUE:
				expr_replace(expr, left);
				return true;
			case COND_FALSE:
				expr_replace(expr, right);
				return true;
			default:
				UNREACHABLE
		}
	}

	Type *left_canonical = left->type->canonical;
	Type *right_canonical = right->type->canonical;
	if (left_canonical != right_canonical)
	{
		Type *max = type_find_max_type(type_no_optional(left_canonical), type_no_optional(right_canonical), left, right);
		if (!max)
		{
			SEMA_ERROR(expr, "Cannot find a common parent type of '%s' and '%s'",
					   type_to_error_string(left->type), type_to_error_string(right->type));
			return false;
		}
		Type *no_fail_max = type_no_optional(max);
		if (!cast_implicit_binary(context, left, no_fail_max, NULL)
			|| !cast_implicit_binary(context, right, no_fail_max, NULL)) return false;
	}

	switch (sema_resolve_storage_type(context, left->type))
	{
		case STORAGE_ERROR:
			return false;
		case STORAGE_COMPILE_TIME:
			if (left->type == type_untypedlist)
			{
				RETURN_SEMA_ERROR(expr, "The ternary would be an 'untyped list', you need to explicitly type one or both branches to a runtime type.");
			}
			RETURN_SEMA_ERROR(expr, "A ternary must always return a runtime type, but it was %s.", type_invalid_storage_type_name(left_canonical));
			break;
		default:
			break;
	}
	if (path != COND_MISSING)
	{
		expr_replace(expr, path == COND_TRUE ? left : right);
	}

	expr_binary_unify_failability(expr, left, right);
	return true;
}

static inline bool sema_expr_analyse_enum_constant(SemaContext *context, Expr *expr, const char *name, Decl *decl)
{
	Decl *enum_constant = decl_find_enum_constant(decl, name);
	if (!enum_constant) return false;

	if (!sema_analyse_decl(context, decl)) return false;

	ASSERT_SPAN(expr, enum_constant->resolve_status != RESOLVE_NOT_DONE);
	expr->type = decl->type;
	if (enum_constant->enum_constant.is_raw)
	{
		expr_replace(expr, copy_expr_single(enum_constant->enum_constant.value));
		return true;
	}
	expr->expr_kind = EXPR_CONST;
	expr->const_expr.const_kind = CONST_ENUM;
	expr->const_expr.enum_val = enum_constant;
	expr->resolve_status = RESOLVE_DONE;
	return true;
}


static inline bool sema_identifier_find_possible_inferred(SemaContext *context, Type *to, Expr *expr)
{
	to = to->canonical;
	switch (to->type_kind)
	{
		case TYPE_CONST_ENUM:
		case TYPE_ENUM:
			if (!decl_ok(to->decl)) return expr_poison(expr), true;
			return sema_expr_analyse_enum_constant(context, expr, expr->unresolved_ident_expr.ident, to->decl);
		default:
			return false;
	}
}

static inline bool sema_expr_analyse_identifier(SemaContext *context, Type *to, Expr *expr)
{
	ASSERT(expr);
	ASSERT_SPAN(expr, expr->unresolved_ident_expr.ident);
	DEBUG_LOG("Resolving identifier '%s'", expr->unresolved_ident_expr.ident);

	ASSERT_SPAN(expr, expr->resolve_status != RESOLVE_DONE);
	DeclId body_param;
	if (!expr->unresolved_ident_expr.path && context->current_macro && (body_param = context->current_macro->func_decl.body_param)) // NOLINT
	{
		if (expr->unresolved_ident_expr.ident == declptr(body_param)->name)
		{
			expr->expr_kind = EXPR_MACRO_BODY_EXPANSION;
			expr->body_expansion_expr.first_stmt = 0;
			expr->body_expansion_expr.declarations = NULL;
			expr->resolve_status = RESOLVE_NOT_DONE;
			expr->type = type_void;
			return true;
		}
	}
	// Just start with inference
	if (!expr->unresolved_ident_expr.path && to)
	{
		if (sema_identifier_find_possible_inferred(context, to, expr)) return expr_ok(expr);
	}

	Decl *decl = sema_find_path_symbol(context, expr->unresolved_ident_expr.ident, expr->unresolved_ident_expr.path);

	// Is this a broken decl?
	if (!decl_ok(decl)) return false;

	// Rerun if we can't do inference.
	if (!decl)
	{
		if (!expr->unresolved_ident_expr.path && expr->unresolved_ident_expr.is_const && (!to || to->canonical->type_kind != TYPE_ENUM))
		{
			CompilationUnit **units = context->unit->module->units;
			FOREACH (CompilationUnit *, unit, units)
			{
				FOREACH(Decl *, decl, unit->enums)
				{
					FOREACH(Decl *, enum_val, decl->enums.values)
					{
						if (enum_val->name == expr->unresolved_ident_expr.ident)
						{
							RETURN_SEMA_ERROR(expr, "No constant named '%s' was found in the current scope. Did you "
							   "mean the value '%s' of the enum '%s'? The enum type cannot be inferred%s, so in that case you need to use "
								"the qualified name: '%s.%s'.",
								enum_val->name, enum_val->name, decl->name, to ? " correctly" : "", decl->name, enum_val->name);
						}
					}
				}
			}
		}
		decl = sema_resolve_symbol(context, expr->unresolved_ident_expr.ident, expr->unresolved_ident_expr.path, expr->span);
		(void)decl;
		ASSERT_SPAN(expr, !decl);
		return false;
	}

	if (decl_needs_prefix(decl))
	{
		if (!sema_analyse_decl(context, decl)) return false;
		if (decl->unit->module != context->unit->module && !expr->unresolved_ident_expr.path)
		{
			const char *message;
			switch (decl->decl_kind)
			{
				case DECL_VAR:
					message = "Globals from other modules must be prefixed with the module name.";
					break;
				case DECL_FUNC:
					message = "Functions from other modules must be prefixed with the module name.";
					break;
				case DECL_MACRO:
					message = "Macros from other modules must be prefixed with the module name.";
					break;
				case DECL_ALIAS:
					message = "Aliases from other modules must be prefixed with the module name.";
					break;
				case DECL_FAULT:
					message = "Faults from other modules must be prefixed with the module name.";
					break;
				default:
					UNREACHABLE
			}
			SEMA_ERROR(expr, message);
			return false;
		}
	}
	if (decl->resolve_status != RESOLVE_DONE)
	{
		if (!sema_analyse_decl(context, decl)) return decl_poison(decl);
	}
	sema_display_deprecated_warning_on_use(context, decl, expr->span);

	unit_register_external_symbol(context, decl);
	decl = decl_flatten(decl);
	if (decl->decl_kind == DECL_VAR)
	{
		decl->var.is_read = true;
		switch (decl->var.kind)
		{
			case VARDECL_CONST:
				if (!decl->type)
				{
					Expr *copy = copy_expr_single(decl->var.init_expr);
					if (!sema_analyse_expr_rvalue(context, copy)) return false;
					if (!expr_is_runtime_const(copy))
					{
						RETURN_SEMA_ERROR(expr, "Constant value did not evaluate to a constant.");
					}
					expr_replace(expr, copy);
					return true;
				}
				break;
			case VARDECL_PARAM:
				if (context->call_env.is_naked_fn && !(context->active_scope.flags & SCOPE_MACRO))
				{
					RETURN_SEMA_ERROR(expr, "Parameters may not be directly accessed in '@naked' functions.");
				}
				break;
			case VARDECL_GLOBAL:
				if (context->call_env.pure)
				{
					RETURN_SEMA_ERROR(expr, "'@pure' functions may not access globals.");
				}
				break;
			default:
				break;
		}
	}
	else if (decl->decl_kind == DECL_FAULT)
	{
		expr_rewrite_const_fault(expr, decl);
		return true;
	}
	expr_resolve_ident(expr, decl);
	return true;
}

static inline bool sema_expr_resolve_ct_identifier(SemaContext *context, Expr *expr)
{
	ASSERT_SPAN(expr, expr->resolve_status != RESOLVE_DONE);
	ASSERT(expr && expr->ct_ident_expr.identifier);

	DEBUG_LOG("Resolving identifier '%s'", expr->ct_ident_expr.identifier);
	Decl *decl = sema_resolve_symbol(context, expr->ct_ident_expr.identifier, NULL, expr->span);
	if (!decl)
	{
		return expr_poison(expr);
	}

	ASSERT_SPAN(expr, decl->decl_kind == DECL_VAR);
	ASSERT_SPAN(expr, decl->resolve_status == RESOLVE_DONE);

	decl->var.is_read = true;
	expr->ct_ident_expr.decl = decl;
	expr->type = decl->type;

	return true;
}


static inline bool sema_expr_analyse_ct_identifier(SemaContext *context, Expr *expr)
{
	if (!sema_expr_resolve_ct_identifier(context, expr)) return false;
	return sema_cast_ct_ident_rvalue(context, expr);
}


static inline bool sema_binary_analyse_with_inference(SemaContext *context, Expr *left, Expr *right, BinaryOp op)
{
	const static int op_table[BINARYOP_LAST + 1] = {
		[BINARYOP_AND] = 1, [BINARYOP_OR] = 1, [BINARYOP_CT_AND] = 1, [BINARYOP_CT_OR] = 1,
		[BINARYOP_EQ] = 2, [BINARYOP_NE] = 2, [BINARYOP_GT] = 2, [BINARYOP_GE] = 2,
		[BINARYOP_LE] = 2, [BINARYOP_LT] = 2 };
	int op_result = op_table[op];
	if (op_result == 1) return true;
	// If lhs or rhs is an initializer list, infer
	bool is_init_rhs = right->expr_kind == EXPR_INITIALIZER_LIST;
	bool is_init_lhs = left->expr_kind == EXPR_INITIALIZER_LIST;
	if (is_init_rhs && is_init_lhs) goto EVAL_BOTH;

	if (is_init_rhs)
	{
		if (!sema_analyse_expr_rvalue(context, left)) return false;
		if (type_kind_is_any_vector(type_flatten(left->type)->type_kind))
		{
			return sema_analyse_inferred_expr(context, left->type, right, NULL);
		}
		return sema_analyse_expr_rvalue(context, right);
	}
	if (is_init_lhs)
	{
		if (!sema_analyse_expr_rvalue(context, right)) return false;
		if (type_kind_is_any_vector(type_flatten(right->type)->type_kind))
		{
			return sema_analyse_inferred_expr(context, right->type, left, NULL);
		}
		return sema_analyse_expr_rvalue(context, left);
	}

	if (op_result != 2) goto EVAL_BOTH;

	if (!sema_analyse_expr_rvalue(context, left)) return false;
	switch (left->type->canonical->type_kind)
	{
		case TYPE_ENUM:
		case TYPE_CONST_ENUM:
			return sema_analyse_inferred_expr(context, left->type, right, NULL);
		default:
			return sema_analyse_expr_rvalue(context, right);
	}

EVAL_BOTH:
	return sema_analyse_expr_rvalue(context, left) && sema_analyse_expr_rvalue(context, right);
}

static inline bool sema_binary_analyse_subexpr(SemaContext *context, Expr *left, Expr *right)
{
	// Special handling of f = FOO_BAR
	if (right->expr_kind == EXPR_UNRESOLVED_IDENTIFIER && right->unresolved_ident_expr.is_const)
	{
		if (!sema_analyse_expr_rvalue(context, left)) return false;
		switch (type_flatten(left->type)->type_kind)
		{
			case TYPE_ENUM:
			case TYPE_CONST_ENUM:
				return sema_analyse_inferred_expr(context, left->type, right, NULL);
			default:
				break;
		}
	}
	// Special handling of f = FOO_BAR
	if (left->expr_kind == EXPR_UNRESOLVED_IDENTIFIER && left->unresolved_ident_expr.is_const)
	{
		if (!sema_analyse_expr_rvalue(context, right)) return false;
		switch (type_flatten(right->type)->type_kind)
		{
			case TYPE_ENUM:
			case TYPE_CONST_ENUM:
				return sema_analyse_inferred_expr(context, right->type, left, NULL);
			default:
				break;
		}
	}
	if (right->expr_kind == EXPR_INITIALIZER_LIST)
	{
		if (!sema_analyse_expr_rvalue(context, left)) return false;
		if (type_kind_is_any_vector(type_flatten(left->type)->type_kind))
		{
			return sema_analyse_inferred_expr(context, left->type, right, NULL);
		}
		return sema_analyse_expr_rvalue(context, right);
	}
	if (left->expr_kind == EXPR_INITIALIZER_LIST)
	{
		if (!sema_analyse_expr_rvalue(context, right)) return false;
		if (type_kind_is_any_vector(type_flatten(right->type)->type_kind))
		{
			return sema_analyse_inferred_expr(context, right->type, left, NULL);
		}
		return sema_analyse_expr_rvalue(context, left);
	}
	return sema_analyse_expr_rvalue(context, left) && sema_analyse_expr_rvalue(context, right);
}

static inline bool sema_binary_analyse_arithmetic_subexpr(SemaContext *context, Expr *expr, const char *error,
                                                          bool bool_and_bitstruct_is_allowed,
                                                          OperatorOverload *operator_overload_ref, bool *failed_ref)
{
	Expr *left = exprptr(expr->binary_expr.left);
	Expr *right = exprptr(expr->binary_expr.right);

	// 1. Analyse both sides.
	ASSERT_SPAN(expr, left->resolve_status == RESOLVE_DONE);
	ASSERT_SPAN(expr, right->resolve_status == RESOLVE_DONE);

	Type *left_type = type_no_optional(left->type)->canonical;
	Type *right_type = type_no_optional(right->type)->canonical;

	if (bool_and_bitstruct_is_allowed)
	{
		if (left_type->type_kind == TYPE_BITSTRUCT && left_type == right_type) return true;
		if (left_type == type_bool && right_type == type_bool) return true;
	}
	// 2. Perform promotion to a common type.
	return sema_binary_arithmetic_promotion(context, left, right, left_type, right_type, expr, error,
		bool_and_bitstruct_is_allowed, operator_overload_ref, failed_ref);
}

static inline int sema_call_find_index_of_named_parameter(SemaContext *context, Decl **func_params, Expr *expr)
{
	const char *name = expr->named_argument_expr.name;
	FOREACH_IDX(i, Decl *, func_param, func_params)
	{
		if (func_param && func_param->name == name) return (int)i;
	}
	SEMA_ERROR(expr, "There's no parameter with the name '%s'.", name);
	return -1;
}

static inline bool sema_call_check_invalid_body_arguments(SemaContext *context, Expr *call, CalledDecl *callee)
{
	Expr *macro_body = exprptrzero(call->call_expr.macro_body);
	Decl **body_arguments = macro_body ? macro_body->macro_body_expr.body_arguments : NULL;
	bool has_body_arguments = vec_size(body_arguments) > 0;

	// 1. Check if there are body arguments but no actual block.
	if (has_body_arguments && !callee->block_parameter)
	{
		if (callee->macro)
		{
			RETURN_SEMA_ERROR(body_arguments[0], "This macro does not support arguments.");
		}
		RETURN_SEMA_ERROR(body_arguments[0], "Only macro calls may have body arguments for a trailing block.");
	}

	// 2. If there is a body then...
	if (macro_body && macro_body->macro_body_expr.body)
	{
		// 2a. If not a macro then this is an error.
		if (!callee->macro)
		{
			RETURN_SEMA_ERROR(call, "Only macro calls may take a trailing block.");
		}
		// 2b. If we don't have a block parameter, then this is an error as well
		if (!callee->block_parameter)
		{
			RETURN_SEMA_ERROR(call, "This macro does not support trailing statements, please remove it.");
		}

		// 2c. This is a macro and it has a block parameter. Everything is fine!
		return true;
	}

	// 3. If we don't have a body, then if it has a block parameter this is an error.
	if (callee->block_parameter)
	{
		ASSERT_SPAN(call, callee->macro);
		RETURN_SEMA_ERROR(call, "Expected call to have a trailing statement, did you forget to add it?");
	}

	// 4. No "body" and no block parameter, this is fine.
	return true;
}

#define RETURN_ERR_WITH_DEFINITION do { if (compiler.context.errors_found != errors) SEMA_NOTE(definition, "The definition is here."); return false; } while (0)

static bool sema_analyse_parameter(SemaContext *context, Expr *arg, Decl *param, Decl *definition, bool *optional_ref,
								   bool *no_match_ref, bool macro, bool is_method_target)
{
	VarDeclKind kind = param->var.kind;
	Type *type = param->type;
	// 16. Analyse a regular argument.
	unsigned errors = compiler.context.errors_found;
	switch (kind)
	{
		case VARDECL_PARAM:
			// foo
			if (arg->expr_kind == EXPR_NAMED_ARGUMENT)
			{
				// This only happens in body arguments
				RETURN_SEMA_ERROR(arg, "Named arguments are not supported for body parameters.");
			}
			if (!sema_analyse_expr_rhs_param(context, type, arg, no_match_ref)) return false;
			if (IS_OPTIONAL(arg)) *optional_ref = true;
			switch (sema_resolve_storage_type(context, arg->type))
			{
				case STORAGE_ERROR:
					return false;
				case STORAGE_NORMAL:
					break;
				case STORAGE_VOID:
				case STORAGE_WILDCARD:
					RETURN_SEMA_ERROR(arg, "A 'void' value cannot be passed as a parameter.");
				case STORAGE_COMPILE_TIME:
					RETURN_SEMA_ERROR(arg, "It is only possible to use %s as a compile time parameter.",
									  type_invalid_storage_type_name(arg->type));
				case STORAGE_UNKNOWN:
					RETURN_SEMA_ERROR(arg, "A value of type '%s' has no known size so cannot be "
										   "passed as a parameter, you can pass a pointer to it though.",
									  type_quoted_error_string(arg->type));
			}
			if (!sema_call_check_contract_param_match(context, param, arg)) RETURN_ERR_WITH_DEFINITION;
			if (!param->alignment)
			{
				ASSERT(macro && "Only in the macro case should we need to insert the alignment.");
				if (!sema_set_alloca_alignment(context, arg->type, &param->alignment)) return false;
			}
			break;
		case VARDECL_PARAM_EXPR:
			if (param->type)
			{
				if (!sema_analyse_expr_rhs_param(context, param->type, arg, NULL))
				{
					RETURN_ERR_WITH_DEFINITION;
				}
			}
			// #foo
			if (context->is_temp)
			{
				SemaContext *temp = context;
				context = MALLOCS(SemaContext);
				*context = *temp;
			}
			{
				Expr *inner = expr_copy(arg);
				arg->expr_kind = EXPR_OTHER_CONTEXT;
				arg->expr_other_context = (ExprOtherContext) { .context = context, .inner = inner };
				arg->resolve_status = RESOLVE_NOT_DONE;
			}
			break;
		case VARDECL_PARAM_CT:
			// $foo
			ASSERT(macro);
			if (!sema_analyse_expr_rhs_param(context, type, arg, no_match_ref))
			{
				RETURN_ERR_WITH_DEFINITION;
			}
			if (!sema_cast_const(arg) && !expr_is_runtime_const(arg))
			{
				if (is_method_target)
				{
					RETURN_SEMA_FUNC_ERROR(definition, arg, "This method is only valid on a compile time constant value.");
				}
				RETURN_SEMA_FUNC_ERROR(definition, arg, "A compile time parameter must always be a constant, did you mistake it for a normal parameter?");
			}
			break;
		case VARDECL_PARAM_CT_TYPE:
			// $Foo
			if (!sema_analyse_expr(context, arg)) RETURN_ERR_WITH_DEFINITION;

			if (arg->expr_kind == EXPR_TYPEINFO)
			{
				assert(arg->type_expr->resolve_status == RESOLVE_DONE);
				expr_rewrite_const_typeid(arg, arg->type_expr->type);
			}
			if (!sema_cast_const(arg) || !expr_is_const_typeid(arg))
			{
				RETURN_SEMA_FUNC_ERROR(definition, arg, "A type, like 'int' or 'double' was expected for the parameter '%s'.", param->name);
			}
			break;
		case VARDECL_CONST:
		case VARDECL_GLOBAL:
		case VARDECL_LOCAL:
		case VARDECL_MEMBER:
		case VARDECL_BITMEMBER:
		case VARDECL_LOCAL_CT:
		case VARDECL_LOCAL_CT_TYPE:
		case VARDECL_UNWRAPPED:
		case VARDECL_REWRAPPED:
		case VARDECL_ERASE:
			UNREACHABLE
	}
	if (type_len_is_inferred(type))
	{
		param->type = type_no_optional(arg->type);
	}
	return true;
}

INLINE bool sema_set_default_argument(SemaContext *context, CalledDecl *callee, Expr *call, Decl *param,
                                      bool *no_match_ref, Expr **expr_ref, bool *optional)
{
	Expr *init_expr = param->var.init_expr;
	if (!init_expr)
	{
		// Handle foo = ... macro init
		if (param->var.no_init)
		{
			param->var.defaulted = true;
		}
		return true;
	}
	Expr *arg = copy_expr_single(init_expr);
	bool parameter_checked = false;
	if (arg->resolve_status != RESOLVE_DONE)
	{
		SemaContext default_context;
		SemaContext *new_context = context_transform_for_eval(context, &default_context, param->unit);
		bool success;
		SCOPE_START
			new_context->original_inline_line = context->original_inline_line ? context->original_inline_line
																			  : call->span.row;
			new_context->original_module = context->original_module;
			success = sema_analyse_parameter(new_context, arg, param, callee->definition, optional, no_match_ref,
											 callee->macro, false);
		SCOPE_END;
		sema_context_destroy(&default_context);
		if (no_match_ref && *no_match_ref) return true;
		if (!success) RETURN_NOTE_FUNC_DEFINITION;
		parameter_checked = true;
	}
	switch (param->var.kind)
	{
		case VARDECL_PARAM_CT:
		case VARDECL_PARAM_CT_TYPE:
		case VARDECL_PARAM_EXPR:
			*expr_ref = arg;
			param->var.defaulted = true;
			if (parameter_checked) return true;
			return sema_analyse_parameter(context, arg, param, callee->definition, optional, no_match_ref,
			                              callee->macro, false);
		default:
			break;
	}
	Expr *function_scope_arg = expr_new(EXPR_DEFAULT_ARG, arg->span);
	function_scope_arg->resolve_status = RESOLVE_DONE;
	function_scope_arg->type = arg->type;
	function_scope_arg->default_arg_expr.inner = arg;
	function_scope_arg->default_arg_expr.loc = callee->call_location;
	*expr_ref = function_scope_arg;
	if (parameter_checked) return true;
	return sema_analyse_parameter(context, function_scope_arg, param, callee->definition, optional, no_match_ref,
								  callee->macro, false);
}


INLINE Expr **sema_splat_arraylike_insert(SemaContext *context, Expr **args, Expr *arg, ArraySize len, ArrayIndex index)
{
	args = sema_prepare_splat_insert(args, len, index);
	if (sema_cast_const(arg))
	{
		for (ArrayIndex i = 0; i < len; i++)
		{
			Expr *expr = expr_copy(arg);
			Expr *subscript = expr_new_expr(EXPR_SUBSCRIPT, expr);
			subscript->subscript_expr.index.expr = exprid(expr_new_const_int(arg->span, type_usz, i));
			subscript->subscript_expr.expr = exprid(expr);
			args[i + index] = subscript;
		}
		return args;
	}
	if (context->call_env.kind != CALL_ENV_FUNCTION)
	{
		SEMA_ERROR(arg, "Cannot splat a non-constant value in a global context.");
		return NULL;
	}
	Decl *temp = decl_new_generated_var(arg->type, VARDECL_LOCAL, arg->span);
	Expr *decl_expr = expr_generate_decl(temp, arg);
	Expr *two = expr_new_expr(EXPR_TWO, arg);
	two->two_expr.first = decl_expr;
	Expr *subscript = expr_new_expr(EXPR_SUBSCRIPT, arg);
	subscript->subscript_expr.index.expr = exprid(expr_new_const_int(arg->span, type_usz, 0));
	subscript->subscript_expr.expr = exprid(expr_variable(temp));
	two->two_expr.last = subscript;
	if (!sema_analyse_expr_rvalue(context, two)) return NULL;
	args[index] = two;
	for (ArrayIndex i = 1; i < len; i++)
	{
		subscript = expr_new_expr(EXPR_SUBSCRIPT, arg);
		subscript->subscript_expr.index.expr = exprid(expr_new_const_int(arg->span, type_usz, i));
		subscript->subscript_expr.expr = exprid(expr_variable(temp));
		args[index + i] = subscript;
	}
	return args;
}

static inline ArrayIndex sema_len_from_expr(Expr *expr)
{
	Type *type = type_flatten(expr->type);
	switch (type->type_kind)
	{
		case TYPE_VECTOR:
		case TYPE_ARRAY:
			return (ArrayIndex)type->array.len;
		case TYPE_UNTYPED_LIST:
			return sema_len_from_const(expr);
		case TYPE_SLICE:
			break;
		default:
			return -1;
	}
	if (sema_cast_const(expr))
	{
		return sema_len_from_const(expr);
	}
	if (expr->expr_kind != EXPR_SLICE) return -1;
	return range_const_len(&expr->slice_expr.range);
}

static Decl *sema_find_splat_arg(Decl *macro, const char *name)
{
	FOREACH(Decl *, decl, macro->func_decl.signature.params)
	{
		if (decl && decl->name == name) return decl;
	}
	return NULL;
}

typedef enum
{
	SPLAT_ZERO,
	SPLAT_ONE,
	SPLAT_NONE,
}  SplatResult;

static SplatResult sema_splat_optional_argument(SemaContext *context, Expr *expr)
{
	Decl *macro = context->current_macro;
	if (!macro) return SPLAT_NONE;
	Decl *candidate = NULL;
	switch (expr->expr_kind)
	{
		case EXPR_UNRESOLVED_IDENTIFIER:
			if (expr->unresolved_ident_expr.path) break;
			candidate = sema_find_splat_arg(macro, expr->unresolved_ident_expr.ident);
			break;
		case EXPR_HASH_IDENT:
			candidate = sema_find_splat_arg(macro, expr->hash_ident_expr.identifier);
			break;
		case EXPR_CT_IDENT:
			candidate = sema_find_splat_arg(macro, expr->ct_ident_expr.identifier);
			break;
		default:
			return SPLAT_NONE;
	}
	if (!candidate) return SPLAT_NONE;
	if (!candidate->var.no_init) return SPLAT_NONE;
	// We found it, it's a valid variable.
	Decl *local = sema_find_local(context, candidate->name);
	if (local && local->var.kind == candidate->var.kind) return SPLAT_ONE;
	// It's missing! Let's splat-zero
	return SPLAT_ZERO;
}

INLINE Type *sema_get_va_type(SemaContext *context, Expr *expr, Variadic variadic)
{
	if (variadic == VARIADIC_RAW)
	{
		if (expr->resolve_status != RESOLVE_DONE)
		{
			expr = copy_expr_single(expr);
			if (!sema_analyse_expr_rvalue(context, expr)) return poisoned_type;
		}
		return type_flatten(expr->type);
	}
	assert(expr->expr_kind == EXPR_MAKE_ANY);
	return type_flatten(expr->make_any_expr.typeid->const_expr.typeid);
}

INLINE bool sema_call_evaluate_arguments(SemaContext *context, CalledDecl *callee, Expr *call, bool *optional,
										 bool *no_match_ref)
{
	// Check body arguments (for macro calls, or possibly broken
	if (!sema_call_check_invalid_body_arguments(context, call, callee)) return false;
	// Pick out all the arguments and parameters.
	Signature *sig = callee->signature;
	unsigned vaarg_index = sig->vararg_index;
	Variadic variadic = sig->variadic;
	Decl **decl_params = callee->params;
	int format_index = (int)sig->attrs.format - 1;
	// If this is a type call, then we have an implicit first argument.
	if (callee->struct_var)
	{
		vec_insert_first(call->call_expr.arguments, callee->struct_var);
		call->call_expr.is_type_method = true;
		ASSERT_SPAN(call, !call->call_expr.is_pointer_call);
	}

	// Zero out all argument slots.
	unsigned func_param_count = vec_size(decl_params);

	// We might have a typed variadic call e.g. foo(int, double...)
	// get that type.
	Type *variadic_type = NULL;
	Type *variadic_slot_type = NULL;
	if (variadic == VARIADIC_TYPED || variadic == VARIADIC_ANY)
	{
		// 7a. The parameter type is <type>[], so we get the <type>
		variadic_slot_type = decl_params[vaarg_index]->type;
		ASSERT_SPAN(call, variadic_slot_type->type_kind == TYPE_SLICE);
		variadic_type = variadic_slot_type->array.base;
	}

	Expr **args = call->call_expr.arguments;
	unsigned num_args = vec_size(args);
	Decl **params = callee->params;

	ASSERT(func_param_count < MAX_PARAMS);
	Expr **actual_args = VECNEW(Expr*, func_param_count);
	for (unsigned i = 0; i < func_param_count; i++)
	{
		vec_add(actual_args, NULL);
	}

	// 2. Loop through the parameters.
	bool has_named = false;
	ArrayIndex last_index = -1;
	Expr *last_named_arg = INVALID_PTR;
	Expr *last = NULL;
	ArrayIndex needed = (ArrayIndex)func_param_count - (callee->struct_var ? 1 : 0);
	for (ArrayIndex i = 0; i < num_args; i++)
	{
		Expr *arg = args[i];
		if (i > 0) last = args[i - 1];
		ASSERT(expr_ok(arg));
		if (arg->expr_kind == EXPR_VASPLAT && context->current_macro)
		{
			Expr **new_args = sema_vasplat_insert(context, args, arg, i);
			if (!new_args) return false;
			args = new_args;
			i--;
			num_args = vec_size(args);
			continue;
		}
		if (arg->expr_kind == EXPR_SPLAT)
		{
			Expr *inner = arg->inner_expr;
			switch (sema_splat_optional_argument(context, inner))
			{
				case SPLAT_ZERO:
					vec_erase_at(args, i);
					i--;
					num_args--;
					continue;
				case SPLAT_ONE:
					expr_replace(arg, inner);
					i--;
					continue;
				case SPLAT_NONE:
					break;
			}
			if (!sema_analyse_expr_rvalue(context, inner)) return false;

			// Let's try fit up a slice to the in the vaslot
			if (variadic_type && i == vaarg_index)
			{
				// Is it not the last and not a named argument, then we do a normal splat.
				if (i + 1 < num_args && args[i + 1]->expr_kind != EXPR_NAMED_ARGUMENT) goto SPLAT_NORMAL;

				// Convert an array/vector to an address of an array.
				Expr *inner_new = inner;
				if (type_is_arraylike(inner->type))
				{
					inner_new = expr_copy(inner);
					expr_insert_addr(inner_new);
				}
				if (!cast_implicit_silent(context, inner_new, variadic_slot_type, false)) goto SPLAT_NORMAL;
				if (inner != inner_new) expr_replace(inner, inner_new);
				// We splat it in the right spot!
				call->call_expr.va_is_splat = true;
				*optional |= IS_OPTIONAL(inner);
				call->call_expr.vasplat = inner;
				continue;
			}
SPLAT_NORMAL:;
			Type *flat = type_flatten(inner->type);
			switch (flat->type_kind)
			{
				case TYPE_VECTOR:
				case TYPE_ARRAY:
				case TYPE_SLICE:
				case TYPE_UNTYPED_LIST:
					// These may be splatted
					break;
				default:
					RETURN_SEMA_ERROR(arg, "An argument of type %s cannot be splatted.",
									  type_quoted_error_string(inner->type));
			}
			// This is the fallback: just splat like vasplat:
			ArrayIndex len = sema_len_from_expr(inner);
			if (len == -1) RETURN_SEMA_ERROR(arg, "Splat may not be used with raw varargs if the length is not known, but if you slice it to a constant length it will work (e.g '...val[:2]')");
			if (len == 0 && !expr_is_const(arg))
			{
				RETURN_SEMA_ERROR(arg, "A non-constant zero size splat cannot be used with raw varargs.");
			}
			Expr **new_args = sema_splat_arraylike_insert(context, args, inner, len, i);
			if (!new_args) return false;
			args = new_args;
			i--;
			num_args = vec_size(args);
			continue;
		}
		if (arg->expr_kind == EXPR_NAMED_ARGUMENT)
		{
			// Find the location of the parameter.
			int index = sema_call_find_index_of_named_parameter(context, params, arg);

			// If it's not found then this is an error. Let's not invoke nomatch
			if (index < 0) return false;

			// We have named parameters, that will add some restrictions.
			has_named = true;

			Decl *param = params[index];
			// 8d. We might actually be finding the typed vararg at the end,
			//     this is an error.
			if (param->var.vararg)
			{
				RETURN_SEMA_FUNC_ERROR(callee->definition, arg, "Vararg parameters may not be named parameters, "
																"use normal parameters instead.", param->name);
			}

			// 8e. We might have already set this parameter, that is not allowed.
			if (actual_args[index] && actual_args[index]->expr_kind != EXPR_DEFAULT_ARG && !param->var.defaulted)
			{
				RETURN_SEMA_ERROR(arg, "The parameter '%s' was already set.", param->name);
			}

			if (last_index > index)
			{
				SEMA_ERROR(arg, "Named arguments must always be declared in order.");
				SEMA_NOTE(last_named_arg, "Place it before this argument.");
				return false;
			}
			if (last_index == -1) last_index = i - 1;
			for (int j = last_index + 1; j < index; j++)
			{
				if (j == vaarg_index) continue;
				if (!sema_set_default_argument(context, callee, call,
				                               params[j], no_match_ref,
				                               &actual_args[j],
				                               optional))
				{
					return false;
				}
			}
			last_index = index;
			last_named_arg = arg;

			actual_args[index] = arg->named_argument_expr.value;
			if (actual_args[index]->expr_kind == EXPR_SPLAT)
			{
				Expr *inner = actual_args[index]->inner_expr;
				switch (sema_splat_optional_argument(context, inner))
				{
					case SPLAT_ZERO:
						if (!sema_set_default_argument(context, callee, call,
											   params[index], no_match_ref,
											   &actual_args[index],
											   optional))
						{
							return false;
						}
						continue;
					case SPLAT_ONE:
						expr_replace(actual_args[index], inner);
						break;
					case SPLAT_NONE:
						break;
				}
			}
			if (!sema_analyse_parameter(context, actual_args[index], param, callee->definition, optional, no_match_ref,
										callee->macro, false)) return false;
			continue;
		}
		if (call->call_expr.va_is_splat)
		{
			if (no_match_ref) goto NO_MATCH_REF;
			RETURN_SEMA_FUNC_ERROR(callee->definition, arg,
								   "This looks like an argument after a splatted variable, which "
								   "isn't allowed. Did you add too many arguments?");
		}
		if (has_named)
		{
			RETURN_SEMA_FUNC_ERROR(callee->definition, args[i - 1],
								   "Named arguments must be placed after positional arguments.");
		}

		// 11. We might have a typed variadic argument.
		if (variadic == VARIADIC_NONE && i >= func_param_count)
		{
			// 15. We have too many parameters...
			if (no_match_ref) goto NO_MATCH_REF;
			RETURN_SEMA_FUNC_ERROR(callee->definition, arg,
								   "This argument would exceed the number of parameters, "
								   "did you add too many arguments?");
		}

		// 10. If we exceed the function parameter count (remember we reduced this by one
		//     in the case of typed vararg) we're now in a variadic list.
		if (variadic != VARIADIC_NONE && i >= vaarg_index)
		{
			switch (variadic)
			{
				case VARIADIC_RAW:
					// Only analyse for non-macro
					if (!callee->macro)
					{
						if (!sema_analyse_expr_rvalue(context, arg)) return false;
						switch (sema_resolve_storage_type(context, arg->type))
						{
							case STORAGE_ERROR:
								return false;
							case STORAGE_NORMAL:
								break;
							default:
								RETURN_SEMA_ERROR(arg, "A value of type %s cannot be passed as a raw variadic argument.",
								                  type_quoted_error_string(arg->type));
						}
						cast_promote_vararg(arg);
					}
					// Set the argument at the location.
					*optional |= IS_OPTIONAL(arg);
					break;
				case VARIADIC_ANY:
					if (!sema_analyse_expr_rvalue(context, arg)) return false;
					Type *type = arg->type;
					switch (sema_resolve_storage_type(context, arg->type))
					{
						case STORAGE_ERROR:
							return false;
						case STORAGE_NORMAL:
							break;
						default:
							RETURN_SEMA_ERROR(arg, "A value of type %s cannot be passed as a variadic argument.",
							                  type_quoted_error_string(type));
					}
					expr_insert_addr(arg);
					FALLTHROUGH;
				case VARIADIC_TYPED:
					if (!sema_analyse_expr_rhs(context, variadic_type, arg, true, no_match_ref, false)) return false;
					*optional |= IS_OPTIONAL(arg);
					break;
				case VARIADIC_NONE:
					UNREACHABLE
			}
			vec_add(call->call_expr.varargs, arg);
			continue;
		}
		if (!sema_analyse_parameter(context, arg, params[i], callee->definition, optional, no_match_ref, callee->macro, callee->struct_var && i == 0)) return false;
		actual_args[i] = arg;
	}
	if (num_args) last = args[num_args - 1];
	call->call_expr.arguments = args;
	// 17. Set default values.
	for (unsigned i = 0; i < func_param_count; i++)
	{
		// 17a. Assigned a value - skip
		if (actual_args[i]) continue;
		if (i == vaarg_index && variadic != VARIADIC_NONE) continue;

		if (!sema_set_default_argument(context, callee, call, params[i], no_match_ref, &actual_args[i],
		                               optional)) return false;
	}
	for (int i = 0; i < func_param_count; i++)
	{
		if (i == vaarg_index) continue;
		if (actual_args[i]) continue;
		Decl *param = params[i];
		if (param->var.no_init) continue; // Macro empty args
		// Argument missing, that's bad.
		if (no_match_ref)
		{
			*no_match_ref = true;
			return true;
		}
		if (!has_named || !param->name)
		{
			int missing = 1;
			for (int j = i + 1; j < func_param_count; j++) if (!actual_args[j]) missing++;
			int implicit_args = callee->struct_var ? 1 : 0;
			if (vec_size(callee->params) == 1 + implicit_args)
			{
				if (param->type)
				{
					RETURN_SEMA_FUNC_ERROR(callee->definition, call,
										   "This call expected a parameter of type %s, did you forget it?",
										   type_quoted_error_string(param->type));
				}
				RETURN_SEMA_FUNC_ERROR(callee->definition, call, "This call expected a parameter, did you forget it?");
			}
			if (variadic != VARIADIC_NONE && i > vaarg_index)
			{
				print_error_after(last->span, "Expected '%s: ...' after this argument.", param->name);
				RETURN_NOTE_FUNC_DEFINITION;
			}
			if (num_args == implicit_args)
			{
				if (missing != needed)
				{
					RETURN_SEMA_FUNC_ERROR(callee->definition, call, "'%s' expects %d-%d parameters, but none was provided.",
										   callee->name, missing, needed);
				}
				RETURN_SEMA_FUNC_ERROR(callee->definition, call, "'%s' expects %d parameter(s), but none was provided.",
									   callee->name, needed);
			}
			if (!last) last = args[0];
			int more_needed = (ArrayIndex)func_param_count - i;
			if (missing != more_needed)
			{
				RETURN_SEMA_FUNC_ERROR(callee->definition, last,
									   "%d-%d additional arguments were expected after this one, did you forget them?",
									   missing, more_needed);
			}
			RETURN_SEMA_FUNC_ERROR(callee->definition, last,
								   "%d more %s expected after this one, did you forget %s?",
								   more_needed, more_needed == 1 ? "argument was" : "arguments were", more_needed == 1 ? "it" : "them");
		}
		RETURN_SEMA_FUNC_ERROR(callee->definition, call, "The parameter '%s' must be set, did you forget it?", param->name);
	}
	call->call_expr.arguments = actual_args;
	if (format_index >= 0) goto CHECK_FORMAT;
	return true;
CHECK_FORMAT:;
	// Check
	Expr *expr = actual_args[format_index];
	if (!sema_cast_const(expr) || call->call_expr.va_is_splat) return true;
	if (!expr_is_const_string(expr))
	{
		RETURN_SEMA_ERROR(expr, "The format string must be a constant string.");
	}
	assert(expr_is_const_string(expr));
	const char *data = expr->const_expr.bytes.ptr;
	size_t len = expr->const_expr.bytes.len;
	size_t idx = 0;
	Expr **vaargs = call->call_expr.varargs;
	unsigned vacount = vec_size(vaargs);
	for (size_t i = 0; i < len; i++)
	{
		if (data[i] != '%') continue;
		i++;
		char c = data[i];
NEXT_FLAG:
		switch (c)
		{
			case '-':
			case '+':
			case '0':
			case '#':
			case ' ':
				if (++i == len) goto UNEXPECTED_END;
				c = data[i];
				goto NEXT_FLAG;
			case '%':
				continue;
			default:
				break;
		}
		if (idx == vacount) goto TOO_FEW_ARGUMENTS;
		expr = vaargs[idx];
		if (!expr) goto TOO_FEW_ARGUMENTS;
		Type *type = sema_get_va_type(context, expr, variadic);
		if (!type_ok(type)) return false;

		// Possible variable width
		if (c == '*')
		{
			if (!type_is_integer(type))
			{
				RETURN_SEMA_ERROR(vaargs[idx], "Expected an integer for the format width.");
			}
			if (++i == len) goto UNEXPECTED_END;
			c = data[i];
			if (++idx == vacount) goto TOO_FEW_ARGUMENTS;
			expr = vaargs[idx];
			if (!expr) goto TOO_FEW_ARGUMENTS;
			type = sema_get_va_type(context, expr, variadic);
			if (!type_ok(type)) return false;
		}
		else
		{
			while (char_is_digit(c))
			{
				if (++i == len) goto UNEXPECTED_END;
				c = data[i];
			}
		}
		if (c == '.')
		{
			if (++i == len) goto UNEXPECTED_END;
			c = data[i];
			if (c == '*')
			{
				if (!type_is_integer(type))
				{
					RETURN_SEMA_ERROR(vaargs[idx], "Expected an integer for the format width.");
				}
				if (++i == len) goto UNEXPECTED_END;
				c = data[i];
				if (++idx == vacount) goto TOO_FEW_ARGUMENTS;
				expr = vaargs[idx];
				if (!expr) goto TOO_FEW_ARGUMENTS;
				type = sema_get_va_type(context, expr, variadic);
				if (!type_ok(type)) return false;
			}
			else
			{
				if (!char_is_digit(c))
				{
					RETURN_SEMA_ERROR(actual_args[format_index], "Expected a format width or '*' in the format string but got another character");
				}
				while (char_is_digit(c))
				{
					if (++i == len) goto UNEXPECTED_END;
					c = data[i];
				}
			}
		}
		switch (c)
		{
			case 's':
				goto NEXT;
			case 'c':
				if (!type_is_integer(type))
				{
					RETURN_SEMA_ERROR(vaargs[idx], "Expected an integer here.");
				}
				goto NEXT;
			case 'd':
			case 'X':
			case 'x':
			case 'B':
			case 'b':
			case 'o':
			case 'a':
			case 'A':
			case 'F':
			case 'f':
			case 'e':
			case 'E':
			case 'g':
			case 'G':
				if (!type_is_number_or_bool(type) && !type_is_pointer_type(type))
				{
					if (type->type_kind == TYPE_ENUM)
					{
						RETURN_SEMA_ERROR(vaargs[idx], "An enum cannot directly be turned into a number. Use '.ordinal' to convert it to its value.", type_quoted_error_string(type));
					}
					RETURN_SEMA_ERROR(vaargs[idx], "Expected a number here, but was %s", type_quoted_error_string(type));
				}
				goto NEXT;
			case 'p':
				if (!type_is_pointer_type(type) && !type_is_integer(type))
				{
					RETURN_SEMA_ERROR(vaargs[idx], "Expected a pointer here.");
				}
				goto NEXT;
			case 'H':
			case 'h':
				if (!type_flat_is_valid_for_arg_h(type))
				{
					RETURN_SEMA_ERROR(vaargs[idx], "Expected a pointer, char array or slice here.");
				}
				goto NEXT;
			default:
				if (c > 31 && c < 127)
				{
					RETURN_SEMA_ERROR(actual_args[format_index], "Unexpected character '%c' in format declaration.", c);
				}
				RETURN_SEMA_ERROR(actual_args[format_index], "Unexpected character in format declaration.", c);
		}
NEXT:
		idx++;
	}
	if (idx < vacount)
	{
		RETURN_SEMA_FUNC_ERROR(callee->definition, call, "Too many arguments were provided for the formatting string.");
	}
	return true;
NO_MATCH_REF:
	*no_match_ref = true;
	return false;
UNEXPECTED_END:
	RETURN_SEMA_FUNC_ERROR(callee->definition, call, "Unexpected end of formatting string mid format declaration.");
TOO_FEW_ARGUMENTS:
	RETURN_SEMA_FUNC_ERROR(callee->definition, call, "Too few arguments provided for the formatting string.");
}

static inline bool sema_call_check_contract_param_match(SemaContext *context, Decl *param, Expr *expr)
{
	if (param->var.not_null && expr_is_const_pointer(expr) && !expr->const_expr.ptr)
	{
		RETURN_SEMA_ERROR(expr, "You may not pass null to the '&' parameter.");
	}
	if (expr->expr_kind == EXPR_UNARY && expr->unary_expr.expr->expr_kind == EXPR_IDENTIFIER)
	{
		if (expr->unary_expr.expr->ident_expr->var.kind == VARDECL_CONST && param->var.out_param)
		{
			SEMA_ERROR(expr, "A const parameter may not be passed into a function or macro as an 'out' or 'inout' argument.");
			return false;
		}
	}

	if (expr->expr_kind != EXPR_IDENTIFIER) return true;
	Decl *ident = expr->ident_expr;
	if (ident->decl_kind != DECL_VAR) return true;
	if (ident->var.out_param && !ident->var.in_param && param->var.in_param)
	{
		RETURN_SEMA_ERROR(expr, "An 'out' parameter may not be passed into a function or macro as an 'in' or 'inout' argument.");
	}
	if (ident->var.in_param && !ident->var.out_param && param->var.out_param)
	{
		RETURN_SEMA_ERROR(expr, "An 'in' parameter may not be passed into a function or macro as an 'out' or 'inout' argument.");
	}
	return true;
}

static inline bool sema_has_require(AstId doc_id)
{
	if (!doc_id) return false;
	Ast *docs = astptr(doc_id);
	while (docs)
	{
		switch (docs->contract_stmt.kind)
		{
			case CONTRACT_UNKNOWN:
			case CONTRACT_COMMENT:
			case CONTRACT_PURE:
			case CONTRACT_PARAM:
			case CONTRACT_OPTIONALS:
			case CONTRACT_ENSURE:
				docs = astptrzero(docs->next);
				continue;
			case CONTRACT_REQUIRE:
				return true;
		}
		UNREACHABLE
	}
	return false;
}

static inline bool sema_call_analyse_func_invocation(SemaContext *context, Decl *decl,
													 Type *type, Expr *expr, Expr *struct_var,
													 bool optional, const char *name, bool *no_match_ref)
{
	Signature *sig = type->function.signature;
	CalledDecl callee = {
			.macro = false,
			.definition = decl,
			.call_location = expr->span,
			.name = name,
			.block_parameter = NULL,
			.struct_var = struct_var,
			.params = sig->params,
			.signature = sig,
	};
	if (context->call_env.pure && !sig->attrs.is_pure && !expr->call_expr.attr_pure)
	{
		SEMA_ERROR(expr, "Only '@pure' functions may be called, you can override this with an attribute.");
		return false;
	}

	if (sig->attrs.noreturn) expr->call_expr.no_return = true;

	if (!sema_call_evaluate_arguments(context, &callee, expr, &optional, no_match_ref)) return false;

	Type *rtype = type->function.prototype->rtype;
	if (expr->call_expr.is_dynamic_dispatch)
	{
		Expr *any_val = expr->call_expr.arguments[0];
		ASSERT(any_val->expr_kind == EXPR_PTR_ACCESS);
		*any_val = *(any_val->inner_expr);
	}
	expr->call_expr.function_contracts = 0;
	AstId docs = decl->func_decl.docs;
	if (!safe_mode_enabled() || !sema_has_require(docs)) goto SKIP_CONTRACTS;
	SemaContext temp_context;
	bool success = false;
	if (!sema_expr_setup_call_analysis(context, &callee, &temp_context,
	                                            expr, NULL, NULL, NULL, NULL,
	                                            NULL, NULL))
	{
		goto END_CONTRACT;
	}
	FOREACH_IDX(i, Decl *, param, sig->params)
	{
		if (!param || !param->name) continue;
		Expr *arg = expr->call_expr.arguments[i];
		if (!arg)
		{
			assert(i == sig->vararg_index);
			if (expr->call_expr.va_is_splat)
			{
				arg = expr->call_expr.vasplat;
			}
			else
			{
				Expr **exprs = expr->call_expr.varargs;
				Expr *init_list = expr_new_expr(EXPR_INITIALIZER_LIST, expr);
				init_list->initializer_list = exprs;
				init_list->type = param->type;
				Expr *compound_init = expr_new_expr(EXPR_COMPOUND_LITERAL, expr);
				compound_init->expr_compound_literal.initializer = init_list;
				compound_init->expr_compound_literal.type_info = type_info_new_base(param->type, param->span);
				if (!sema_analyse_expr_rvalue(context, compound_init)) goto END_CONTRACT;
				arg = compound_init;
				expr->call_expr.va_is_splat = true;
			}
		}
		Decl *new_param = decl_new_generated_var(arg->type, VARDECL_LOCAL, expr->span);
		new_param->name = param->name;
		new_param->unit = context->unit;
		new_param->var.copy_const = true;
		Expr *new_arg = expr_generate_decl(new_param, arg);
		new_arg->resolve_status = RESOLVE_DONE;
		new_arg->type = arg->type;
		if (!sema_add_local(&temp_context, new_param)) goto END_CONTRACT;
		if (IS_OPTIONAL(new_param))
		{
			sema_unwrap_var(&temp_context, new_param);
		}
		if (i == sig->vararg_index)
		{
			expr->call_expr.vasplat = new_arg;
			continue;
		}
		expr->call_expr.arguments[i] = new_arg;

	}
	AstId assert_first = 0;
	AstId* next = &assert_first;

	if (!sema_analyse_contracts(&temp_context, docs, &next, expr->span, NULL)) return false;

	expr->call_expr.function_contracts = assert_first;

	success = true;
END_CONTRACT:
	sema_context_destroy(&temp_context);
	if (!success) return false;

SKIP_CONTRACTS:
	expr->call_expr.has_optional_arg = optional;

	if (!type_is_void(rtype))
	{
		bool is_optional_return = type_is_optional(rtype);
		expr->call_expr.is_optional_return = is_optional_return;
		expr->call_expr.must_use = sig->attrs.nodiscard || (is_optional_return && !sig->attrs.maydiscard);
	}
	expr->type = type_add_optional(rtype, optional);
	return true;
}

static inline bool sema_expr_analyse_var_call(SemaContext *context, Expr *expr, Expr *var, Type *func_ptr_type, bool optional,bool *no_match_ref)
{
	func_ptr_type = type_flat_distinct_inline(func_ptr_type);
	if (func_ptr_type->type_kind != TYPE_FUNC_PTR)
	{
		if (no_match_ref)
		{
			*no_match_ref = true;
			return false;
		}
		RETURN_SEMA_ERROR(expr, "Only macros, functions and function pointers may be invoked, this is of type '%s'.",
						  type_to_error_string(func_ptr_type));
	}
	if (sema_cast_const(var) && expr_is_const_pointer(var) && var->const_expr.ptr == 0)
	{
		RETURN_SEMA_ERROR(var, "You cannot call a null function pointer.");
	}
	Type *pointee = func_ptr_type->pointer;
	expr->call_expr.is_pointer_call = true;
	return sema_call_analyse_func_invocation(context, pointee->function.decl, pointee, expr, NULL, optional,
											 func_ptr_type->pointer->name,
											 no_match_ref);
}

// Unify returns in a macro or expression block.
static inline Type *context_unify_returns(SemaContext *context)
{
	bool all_returns_need_casts = false;
	Type *common_type = NULL;

	// 1. Loop through the returns.
	bool optional = false;
	unsigned returns = vec_size(context->block_returns);
	if (!returns) return type_void;
	for (unsigned i = 0; i < returns; i++)
	{
		Ast *return_stmt = context->block_returns[i];
		Type *rtype;
		if (!return_stmt)
		{
			optional = true;
			rtype = type_wildcard;
		}
		else
		{
			Expr *ret_expr = return_stmt->return_stmt.expr;
			rtype = ret_expr ? ret_expr->type : type_void;
			if (type_is_optional(rtype))
			{
				optional = true;
				rtype = type_no_optional(rtype);
			}
		}
		// 2. If we have no common type, set to the return type.
		if (!common_type)
		{
			common_type = rtype;
			continue;
		}

		// 3. Same type -> we're done.
		if (common_type == rtype || (type_is_void(common_type) && rtype == type_wildcard)) continue;

		// 4. Find the max of the old and new.
		Type *max = type_find_max_type(common_type, rtype, NULL, NULL);

		// 5. No match -> error.
		if (!max)
		{
			ASSERT(return_stmt);
			SEMA_ERROR(return_stmt, "Cannot find a common parent type of %s and %s",
					   type_quoted_error_string(rtype), type_quoted_error_string(common_type));
			Ast *prev = context->block_returns[i - 1];
			ASSERT(prev);
			SEMA_NOTE(prev, "The previous return was here.");
			return NULL;
		}

		// 6. Set the new max, mark as needing a cast on all returns.
		common_type = max;
		all_returns_need_casts = true;
	}

	ASSERT(common_type);

	// 7. Insert casts.
	if (all_returns_need_casts)
	{
		ASSERT(common_type != type_wildcard);
		FOREACH(Ast *, return_stmt, context->block_returns)
		{
			if (!return_stmt) continue;
			Expr *ret_expr = return_stmt->return_stmt.expr;
			if (!ret_expr)
			{
				if (type_is_void(common_type)) continue;
				context_unify_returns(context);
			}
			// 8. All casts should work.
			if (!cast_implicit(context, ret_expr, common_type, false))
			{
				return NULL;
			}
		}
	}

	return type_add_optional(common_type, optional);
}

static inline bool sema_expr_analyse_func_call(SemaContext *context, Expr *expr, Decl *decl, Expr *struct_var,
											   bool optional, bool *no_match_ref)
{
	expr->call_expr.is_pointer_call = false;
	if (decl->func_decl.attr_test)
	{
		SEMA_ERROR(expr, "@test functions may not be directly called.");
		return false;
	}

	if (decl->func_decl.attr_benchmark)
	{
		SEMA_ERROR(expr, "@benchmark functions may not be directly called.");
		return false;
	}

	sema_display_deprecated_warning_on_use(context, decl, expr->span);

	// Tag dynamic dispatch.
	if (struct_var && decl->func_decl.attr_interface_method) expr->call_expr.is_dynamic_dispatch = true;

	return sema_call_analyse_func_invocation(context, decl,
											 decl->type,
											 expr,
											 struct_var,
											 optional,
											 decl->name, no_match_ref);
}

static inline bool sema_expr_setup_call_analysis(SemaContext *context, CalledDecl *callee,
                                                 SemaContext *macro_context, Expr *call_expr,
                                                 Type *rtype,
                                                 Ast *yield_body,
                                                 Decl **yield_params, Decl **params,
                                                 BlockExit **block_exit_ref, InliningSpan *span_ref)
{

	Decl *decl = callee->definition;
	sema_context_init(macro_context, decl->unit);
	macro_context->compilation_unit = context->unit;
	macro_context->macro_call_depth = context->macro_call_depth + 1;
	macro_context->call_env = context->call_env;
	macro_context->expected_block_type = rtype;
	if (span_ref)
	{
		*span_ref = (InliningSpan){ call_expr->span, context->inlined_at };
	}
	else
	{
		span_ref = context->inlined_at;
	}
	macro_context->inlined_at = span_ref;
	macro_context->current_macro = callee->macro ? decl : NULL;
	macro_context->yield_body = yield_body;
	macro_context->yield_params = yield_params;
	macro_context->yield_context = context;
	FOREACH(Expr *, expr, call_expr->call_expr.varargs)
	{
		if (expr->resolve_status == RESOLVE_DONE) continue;
		Expr *expr_inner = expr_copy(expr);
		expr->expr_kind = EXPR_OTHER_CONTEXT;
		expr->expr_other_context.inner = expr_inner;
		expr->expr_other_context.context = context;
	}
	macro_context->macro_has_vaargs = callee->macro && callee->signature->variadic == VARIADIC_RAW;
	macro_context->macro_varargs = macro_context->macro_has_vaargs ? call_expr->call_expr.varargs : NULL;
	macro_context->original_inline_line = context->original_inline_line ? context->original_inline_line : call_expr->span.row;
	macro_context->original_module = context->original_module ? context->original_module : context->compilation_unit->module;
	macro_context->macro_params = params;

	macro_context->block_exit_ref = block_exit_ref;

	context_change_scope_with_flags(macro_context, SCOPE_MACRO);
	macro_context->block_return_defer = macro_context->active_scope.defer_last;

	return true;
}
bool sema_expr_analyse_macro_call(SemaContext *context, Expr *call_expr, Expr *struct_var, Decl *decl,
								  bool call_var_optional, bool *no_match_ref)
{
	bool is_always_const = decl->func_decl.signature.attrs.always_const;
	if (decl->resolved_attributes && decl->attrs_resolved && decl->attrs_resolved->links)
	{
		if (context->call_env.kind != CALL_ENV_FUNCTION && context->call_env.kind != CALL_ENV_FUNCTION_STATIC)
		{
			goto SKIP_LINK;
		}
		Decl *func = context->call_env.current_function;
		ASSERT_SPAN(func, func->resolved_attributes);
		if (!func->attrs_resolved)
		{
			func->attrs_resolved = MALLOCS(ResolvedAttrData);
			*func->attrs_resolved = (ResolvedAttrData) { .overload = INVALID_SPAN };
		}
		const char **updated = func->attrs_resolved->links;
		FOREACH(const char *, link, decl->attrs_resolved->links)
		{
			vec_add(updated, link);
		}
		func->attrs_resolved->links = updated;
	}
	SKIP_LINK:;
	bool is_outer = call_expr->call_expr.is_outer_call;
	ASSERT_SPAN(call_expr, decl->decl_kind == DECL_MACRO);

	if (context->macro_call_depth > MAX_MACRO_RECURSION_DEPTH)
	{
		decl->decl_kind = DECL_POISONED;
		RETURN_SEMA_ERROR(call_expr, "Failure evaluating macro, max call depth reached, "
									 "possibly due non-terminating macro recursion.");
	}

	sema_display_deprecated_warning_on_use(context, decl, call_expr->span);

	copy_begin();
	Decl **params = copy_decl_list_macro(decl->func_decl.signature.params);
	Ast *body = copy_ast_macro(astptr(decl->func_decl.body));
	AstId docs = decl->func_decl.docs;
	if (docs) docs = astid(copy_ast_macro(astptr(docs)));
	Signature *sig = &decl->func_decl.signature;
	copy_end();
	CalledDecl callee = {
			.macro = true,
			.call_location = call_expr->span,
			.name = decl->name,
			.params = params,
			.definition = decl,
			.block_parameter = decl->func_decl.body_param ? declptr(decl->func_decl.body_param)->name : NULL,
			.signature = sig,
			.struct_var = struct_var
	};

	bool has_optional_arg = call_var_optional;
	if (!sema_call_evaluate_arguments(context, &callee, call_expr, &has_optional_arg, no_match_ref)) return false;

	unsigned vararg_index = sig->vararg_index;
	Expr **args = call_expr->call_expr.arguments;
	FOREACH_IDX(i, Decl *, param, params)
	{
		if (i == vararg_index)
		{
			if (!param) continue;
			// Splat? That's the simple case.
			if (call_expr->call_expr.va_is_splat)
			{
				if (!sema_analyse_expr_rvalue(context, args[i] = call_expr->call_expr.vasplat)) return false;
			}
			else
			{
				Expr **exprs = call_expr->call_expr.varargs;
				Expr *literal = expr_new(EXPR_COMPOUND_LITERAL, exprs ? exprs[0]->span : param->span);
				Expr *initializer_list = expr_new_expr(EXPR_INITIALIZER_LIST, literal);
				initializer_list->initializer_list = exprs;
				literal->expr_compound_literal.type_info = vartype(param);
				literal->expr_compound_literal.initializer = initializer_list;
				if (!sema_analyse_expr_rvalue(context, args[i] = literal)) return false;
			}
		}
		param->var.init_expr = args[i];
		if (!args[i]) continue;
		// Lazy arguments doesn't affect optional arg.
		if (param->var.kind == VARDECL_PARAM_EXPR) continue;
		has_optional_arg = has_optional_arg || IS_OPTIONAL(args[i]);
	}

	Expr *macro_body = exprptrzero(call_expr->call_expr.macro_body);
	Decl **body_params = macro_body ? macro_body->macro_body_expr.body_arguments : NULL;
	unsigned body_params_count = vec_size(body_params);
	Decl **macro_body_params = decl->func_decl.body_param ? declptr(decl->func_decl.body_param)->body_params : NULL;
	unsigned expected_body_params = vec_size(macro_body_params);
	if (expected_body_params > body_params_count)
	{
		if (no_match_ref) goto NO_MATCH_REF;
		RETURN_SEMA_ERROR(call_expr, "Not enough parameters for the macro body, expected %d.", expected_body_params);
	}
	if (expected_body_params < body_params_count)
	{
		if (no_match_ref) goto NO_MATCH_REF;
		RETURN_SEMA_ERROR(call_expr, "Too many parameters for the macro body, expected %d.", expected_body_params);
	}
	for (unsigned j = 0; j < expected_body_params; j++)
	{
		Decl *body_param = macro_body_params[j];
		ASSERT_SPAN(call_expr, body_param->resolve_status == RESOLVE_DONE);
		Decl *body_arg = body_params[j];
		VarDeclKind kind_of_expected = body_param->var.kind;
		if (kind_of_expected != body_arg->var.kind)
		{
			switch (kind_of_expected)
			{
				case VARDECL_PARAM_CT_TYPE:
					RETURN_SEMA_FUNC_ERROR(decl, body_arg, "Expected a type argument.");
				case VARDECL_PARAM_EXPR:
					RETURN_SEMA_FUNC_ERROR(decl, body_arg, "Expected an expression argument.");
				case VARDECL_PARAM_CT:
					RETURN_SEMA_FUNC_ERROR(decl, body_arg, "Expected a compile time ('$') argument.");
				case VARDECL_PARAM:
					RETURN_SEMA_FUNC_ERROR(decl, body_arg, "Expected a regular argument.");
				default:
					UNREACHABLE
			}
		}
		// No type checking
		switch (kind_of_expected)
		{
			case VARDECL_PARAM_CT_TYPE:
				continue;
			case VARDECL_PARAM_CT:
			case VARDECL_PARAM_EXPR:
			case VARDECL_PARAM:
				// Optional typing
				break;
			default:
				UNREACHABLE
		}
		if (body_arg->var.init_expr)
		{
			RETURN_SEMA_ERROR(body_arg->var.init_expr, "Macro body parameters should never have default values.");
		}
		TypeInfo *expected_type_info = vartype(body_param);
		TypeInfo *type_info = vartype(body_arg);
		if (type_info && !sema_resolve_type_info(context, type_info, RESOLVE_TYPE_DEFAULT)) return false;
		Type *type = type_info ? type_info->type : NULL;
		if (!type && expected_type_info)
		{
			type = expected_type_info->type;
		}
		if (type && expected_type_info && type->canonical != expected_type_info->type->canonical)
		{
			if (no_match_ref) goto NO_MATCH_REF;
			RETURN_SEMA_ERROR(type_info, "This parameter should be %s but was %s",
								  type_quoted_error_string(expected_type_info->type),
								  type_quoted_error_string(type));
		}
		body_arg->type = type;

		if (type_info)
		{
			switch (sema_resolve_storage_type(context, type_info->type))
			{
				case STORAGE_ERROR:
					return false;
				case STORAGE_NORMAL:
					if (!sema_set_alloca_alignment(context, body_arg->type, &body_arg->alignment)) return false;
					break;
				default:
					break;
			}
		}
	}


	DynamicScope old_scope = context->active_scope;
	// Create a scope, since the macro itself will not.
	context_change_scope_with_flags(context, SCOPE_NONE);
	SemaContext macro_context;

	Type *rtype = typeget(sig->rtype);
	bool optional_return = rtype && type_is_optional(rtype);
	bool may_be_optional = !rtype || optional_return;
	if (rtype) rtype = type_no_optional(rtype);

	BlockExit** block_exit_ref = CALLOCS(BlockExit*);

	InliningSpan span;
	if (!sema_expr_setup_call_analysis(context, &callee, &macro_context,
	                                   call_expr, rtype, macro_body ? macro_body->macro_body_expr.body : NULL, body_params, params, block_exit_ref,
	                                   &span))
	{
		goto EXIT_FAIL;
	}

	AstId assert_first = 0;
	AstId* next = &assert_first;

	FOREACH_IDX(idx, Decl *, param, params)
	{
		// Skip raw vararg
		if (!param) continue;
		if (param->var.no_init && param->var.defaulted) continue;
		if (!sema_add_local(&macro_context, param)) goto EXIT_FAIL;
		if (param->var.init_expr)
		{
			Type *param_type = param->type;
			if (param_type && param->var.type_info && (param->var.out_param || param->var.not_null))
			{
				param_type = type_flatten(param_type);
				if (param_type->type_kind != TYPE_POINTER && param_type->type_kind != TYPE_SLICE && param_type->type_kind != TYPE_INTERFACE && param_type->type_kind != TYPE_ANY)
				{
					SEMA_ERROR(param->var.init_expr, "Expected a pointer, slice or interface value for '%s'.", param->name);
					goto EXIT_FAIL;
				}
			}
			if (param->var.not_null && param_type && (param_type->type_kind == TYPE_POINTER || param_type->type_kind == TYPE_SLICE || param_type->type_kind == TYPE_INTERFACE || param_type->type_kind == TYPE_ANY))
			{
				Expr *expr = expr_variable(param);
				Expr *binary = expr_new_expr(EXPR_BINARY, expr);
				binary->binary_expr.left = exprid(expr);
				binary->binary_expr.right = exprid(expr_new_const_null(expr->span, type_voidptr));
				binary->binary_expr.operator = BINARYOP_NE;
				if (!sema_analyse_expr_rhs(context, type_bool, binary, false, NULL, false)) goto EXIT_FAIL;
				const char *string = struct_var && idx == 0 ? "Called a method on a null value." : "Passed null to a ref ('&') parameter.";
				if (expr_is_const_bool(binary) && !binary->const_expr.b)
				{
					sema_error_at(context, param->var.init_expr->span, string);
					goto EXIT_FAIL;
				}

				Ast *assert = new_ast(AST_ASSERT_STMT, param->var.init_expr->span);
				assert->assert_stmt.is_ensure = true;
				assert->assert_stmt.expr = exprid(binary);
				Expr *comment_expr = expr_new_const_string(expr->span, string);
				assert->assert_stmt.message = exprid(comment_expr);
				ast_append(&next, assert);
			}
		}
	}

	bool has_ensures = false;
	if (!sema_analyse_contracts(&macro_context, docs, &next, call_expr->span, &has_ensures)) return false;
	macro_context.macro_has_ensures = has_ensures;

	sema_append_contract_asserts(assert_first, body);
	if (!sema_analyse_statement(&macro_context, body)) goto EXIT_FAIL;
	ASSERT_SPAN(call_expr, macro_context.active_scope.depth == 1);
	bool implicit_void_return = !macro_context.active_scope.end_jump.active;
	params = macro_context.macro_params;
	bool is_no_return = sig->attrs.noreturn;

	if (!vec_size(macro_context.block_returns))
	{
		if (rtype && rtype != type_void && !macro_context.active_scope.end_jump.active)
		{
			SEMA_ERROR(decl,
					   "Missing return in macro that should evaluate to %s.",
					   type_quoted_error_string(rtype));
			goto EXIT_FAIL;
		}
	}
	else if (is_no_return)
	{
		SEMA_ERROR(macro_context.block_returns[0], "Return used despite macro being marked '@noreturn'.");
		goto EXIT_FAIL;
	}

	if (rtype)
	{
		bool inferred_len = type_len_is_inferred(rtype);
		FOREACH(Ast *, return_stmt, macro_context.block_returns)
		{
			if (!return_stmt)
			{
				ASSERT_SPAN(call_expr, may_be_optional);
				continue;
			}
			Expr *ret_expr = return_stmt->return_stmt.expr;
			if (!ret_expr)
			{
				if (type_is_void(rtype)) continue;
				SEMA_ERROR(return_stmt, "Expected returning a value of type %s.", type_quoted_error_string(rtype));
				goto EXIT_FAIL;
			}
			Type *type = ret_expr->type;
			if (inferred_len)
			{
				Type *flattened = type_flatten(type);
				if (flattened->type_kind == TYPE_ARRAY && rtype->type_kind == TYPE_INFERRED_ARRAY)
				{
					rtype = type_get_array(rtype->array.base, flattened->array.len);
					inferred_len = false;
				}
				else if (flattened->type_kind == TYPE_VECTOR && rtype->type_kind == TYPE_INFERRED_VECTOR)
				{
					rtype = type_get_vector(rtype->array.base, flattened->array.len);
					inferred_len = false;
				}
			}
			bool success = cast_implicit_silent(context, ret_expr, rtype, false);
			if (inferred_len || (!may_be_optional && IS_OPTIONAL(ret_expr)) || !success)
			{
				SEMA_ERROR(ret_expr, "Expected %s, not %s.", type_quoted_error_string(rtype),
						   type_quoted_error_string(type));
				goto EXIT_FAIL;
			}
			ret_expr->type = type_add_optional(ret_expr->type, optional_return);
		}
	}
	else
	{
		rtype = context_unify_returns(&macro_context);
		if (!rtype) goto EXIT_FAIL;
		optional_return = type_is_optional(rtype);
	}

	// Handle the implicit return.
	if (implicit_void_return)
	{
		if (type_is_wildcard(rtype))
		{
			optional_return = optional_return || type_is_optional(rtype);
			rtype = type_void;
		}
		else
		{
			Type *flat = type_flatten(rtype);
			if (flat != type_void)
			{
				RETURN_SEMA_ERROR(decl, "Macro implicitly returns 'void' at the end, which cannot be cast to the inferred %s.",
								  type_quoted_error_string(rtype));
			}
		}
	}

	call_expr->type = type_add_optional(rtype, optional_return || has_optional_arg);
	if (is_no_return && type_is_void(rtype))
	{
		call_expr->type = type_wildcard;
	}

	ASSERT_SPAN(call_expr, call_expr->type);
	bool must_use = false;
	if (rtype != type_void || optional_return)
	{
		must_use = sig->attrs.nodiscard || (optional_return && !sig->attrs.maydiscard);
	}
	unsigned returns_found = vec_size(macro_context.block_returns);
	// We may have zero normal macro returns but the active scope still has a "jump end".
	// In this case it is triggered by the @body()
	if (!returns_found && macro_context.active_scope.end_jump.active)
	{
		is_no_return = true;
	}
	if (returns_found == 1 && !implicit_void_return)
	{
		Ast *ret = macro_context.block_returns[0];
		Expr *result = ret ? ret->return_stmt.expr : NULL;
		if (!result) goto NOT_CT;
		if (!expr_is_runtime_const(result)) goto NOT_CT;
		FOREACH(Decl *, param, params)
		{
			// Skip raw vararg
			if (!param) continue;
			switch (param->var.kind)
			{
				case VARDECL_PARAM_CT:
				case VARDECL_PARAM_CT_TYPE:
				case VARDECL_PARAM_EXPR:
					break;
				default:
					goto NOT_CT;
			}
		}
		if (ast_is_compile_time(body))
		{
			expr_replace(call_expr, result);
			goto EXIT;
		}
	}
NOT_CT:
	call_expr->expr_kind = EXPR_MACRO_BLOCK;
	call_expr->macro_block.had_optional_arg = has_optional_arg;
	call_expr->macro_block.is_must_use = must_use;
	call_expr->macro_block.is_optional_return = optional_return;
	call_expr->macro_block.first_stmt = body->compound_stmt.first_stmt;
	call_expr->macro_block.macro = decl;
	call_expr->macro_block.params = params;
	call_expr->macro_block.block_exit = block_exit_ref;
	call_expr->macro_block.is_noreturn = is_no_return;
EXIT:
	if (is_outer && !type_is_void(call_expr->type))
	{
		RETURN_SEMA_ERROR(call_expr, "The macro itself returns %s here, but only 'void' is permitted "
							   "when a macro with trailing body is used directly after '=>'.",
							   type_quoted_error_string(rtype));
	}
	ASSERT_SPAN(call_expr, context->active_scope.defer_last == context->active_scope.defer_start);
	context->active_scope = old_scope;
	if (is_no_return)
	{
		SET_JUMP_END(context, call_expr);
	}
	sema_context_destroy(&macro_context);
	call_expr->resolve_status = RESOLVE_DONE;
	if (!expr_is_runtime_const(call_expr))
	{
		if (is_always_const)
		{
			SEMA_ERROR(call_expr, "The macro failed to fold to a constant value, despite being '@const'.");
			SEMA_NOTE(decl, "The macro was declared here.");
			return false;
		}
		if (call_expr->type == type_untypedlist)
		{
			SEMA_ERROR(call_expr, "The macro returns an untyped list, but the macro did not evaluate to a constant. The macro needs to explicitly cast the return value to the expected type.");
			SEMA_NOTE(decl, "The macro was declared here.");
			return false;
		}
	}
	return true;
EXIT_FAIL:
	context->active_scope = old_scope;
	sema_context_destroy(&macro_context);
	return SCOPE_POP_ERROR();
NO_MATCH_REF:
	*no_match_ref = true;
	return false;
}

static bool sema_call_analyse_body_expansion(SemaContext *macro_context, Expr *call)
{
	Decl *macro = macro_context->current_macro;
	ASSERT_SPAN(call, macro && macro->func_decl.body_param);
	Decl *body_decl = declptr(macro->func_decl.body_param);
	ExprCall *call_expr = &call->call_expr;
	Expr *macro_body = exprptrzero(call_expr->macro_body);
	if (macro_body && vec_size(macro_body->macro_body_expr.body_arguments))
	{
		PRINT_ERROR_AT(call, "Nested expansion is not possible.");
		return false;
	}
	if (call_expr->va_is_splat)
	{
		PRINT_ERROR_AT(call, "Expanding parameters is not allowed for macro invocations.");
	}
	// Theoretically we could support named arguments, but that's unnecessary.
	unsigned expressions = vec_size(call_expr->arguments);
	Decl **body_parameters = body_decl->body_params;
	if (expressions != vec_size(body_parameters))
	{
		PRINT_ERROR_AT(call, "Expected %d parameter(s) to %s.", vec_size(body_parameters), body_decl->name);
		return false;
	}
	Expr **args = call_expr->arguments;

	Decl **params = macro_context->yield_params;

	bool has_optional_arg = false;

	// Evaluate the expressions.
	for (unsigned i = 0; i < expressions; i++)
	{
		Decl *param = params[i];
		Expr *expr = args[i];
		if (!sema_analyse_parameter(macro_context, expr, param, body_decl, &has_optional_arg, NULL, true, false))
		{
			return false;
		}
		if (has_optional_arg)
		{
			sema_error_at(macro_context, expr->span, "Optional arguments are not permitted in a body invocation.");
			return false;
		}
		switch (param->var.kind)
		{
			case VARDECL_PARAM_EXPR:
			case VARDECL_PARAM_CT:
			case VARDECL_PARAM_CT_TYPE:
				param->var.init_expr = args[i];
				args[i] = NULL;
				break;
			default:
				break;
		}
	}

	AstId macro_defer = macro_context->active_scope.defer_last;
	Ast *first_defer = NULL;
	SemaContext *context = macro_context->yield_context;
	Expr *func_expr = exprptr(call_expr->function);
	ASSERT_SPAN(call, func_expr->expr_kind == EXPR_MACRO_BODY_EXPANSION);
	expr_replace(call, func_expr); // NOLINT
	call->body_expansion_expr.values = args;
	call->body_expansion_expr.declarations = macro_context->yield_params;
	AstId last_defer = context->active_scope.defer_last;
	SCOPE_START
		unsigned ct_context = sema_context_push_ct_stack(context);
		if (macro_defer)
		{
			Ast *macro_defer_ast = astptr(macro_defer);
			first_defer = macro_defer_ast;
			while (first_defer->defer_stmt.prev_defer)
			{
				first_defer = astptr(first_defer->defer_stmt.prev_defer);
			}
			first_defer->defer_stmt.prev_defer = context->active_scope.defer_last;
			context->active_scope.defer_last = macro_defer;
		}
		FOREACH(Decl *, param, params)
		{
			if (!sema_add_local(context, param))
			{
				sema_context_pop_ct_stack(context, ct_context);
				return SCOPE_POP_ERROR();
			}
		}
		Ast *ast = copy_ast_single(macro_context->yield_body);
		call->body_expansion_expr.first_stmt = astid(ast);
		if (!sema_analyse_statement(context, ast))
		{
			sema_context_pop_ct_stack(context, ct_context);
			return SCOPE_POP_ERROR();
		}
		ASSERT_SPAN(call, ast->ast_kind == AST_COMPOUND_STMT);
		if (context->active_scope.end_jump.active)
		{
			macro_context->active_scope.end_jump = context->active_scope.end_jump;
		}
		if (first_defer)
		{
			first_defer->defer_stmt.prev_defer = 0;
			context->active_scope.defer_last = last_defer;
		}
		sema_context_pop_ct_stack(context, ct_context);
	SCOPE_END;

	return true;

}

// Conversion MyEnum.FOO -> MyEnum.ordinal, will change the type.
void sema_expr_convert_enum_to_int(Expr *expr)
{
	ASSERT(type_flatten(expr->type)->type_kind == TYPE_ENUM);
	Type *underlying_type = type_base(expr->type);
	if (sema_cast_const(expr))
	{
		ASSERT(expr_is_const_enum(expr));
		expr_rewrite_const_int(expr, underlying_type, expr->const_expr.enum_val->enum_constant.inner_ordinal);
	}
	if (expr->expr_kind == EXPR_ENUM_FROM_ORD)
	{
		*expr = *expr->inner_expr;
	}
	expr->type = type_add_optional(underlying_type, IS_OPTIONAL(expr));
}

bool sema_expr_analyse_general_call(SemaContext *context, Expr *expr, Decl *decl, Expr *struct_var, bool optional,
									bool *no_match_ref)
{
	expr->call_expr.is_type_method = struct_var != NULL;
	if (decl == NULL)
	{
		Expr *var = exprptr(expr->call_expr.function);
		return sema_expr_analyse_var_call(context, expr, var, type_flatten(var->type),
		                                  optional, no_match_ref);
	}
	if (!sema_analyse_decl(context, decl)) return false;
	switch (decl->decl_kind)
	{
		case DECL_MACRO:
			expr->call_expr.func_ref = declid(decl);
			expr->call_expr.is_func_ref = true;
			return sema_expr_analyse_macro_call(context, expr, struct_var, decl, optional, no_match_ref);
		case DECL_VAR:
		{
			ASSERT_SPAN(expr, struct_var == NULL);
			Expr *var = exprptr(expr->call_expr.function);
			ASSERT_SPAN(expr, var);
			return sema_expr_analyse_var_call(context, expr, var, decl->type->canonical,
											  optional || IS_OPTIONAL(decl), no_match_ref);

		}
		case DECL_FUNC:
			expr->call_expr.func_ref = declid(decl);
			expr->call_expr.is_func_ref = true;
			return sema_expr_analyse_func_call(context, expr, decl, struct_var, optional, no_match_ref);
		case DECL_POISONED:
			return false;
		default:
			if (no_match_ref)
			{
				*no_match_ref = true;
				return false;
			}
			RETURN_SEMA_ERROR(expr, "This expression cannot be called.");
	}
}

INLINE bool sema_expr_analyse_from_ordinal(SemaContext *context, Expr *expr, Expr *tag)
{
	Expr **args = expr->call_expr.arguments;
	unsigned arg_count = vec_size(args);
	Decl *decl = tag->type_call_expr.type;
	if (arg_count != 1) RETURN_SEMA_ERROR(expr, "Expected a single integer argument to 'from_ordinal'.");
	Expr *key = args[0];
	if (!sema_analyse_expr_rvalue(context, key)) return false;
	if (!type_is_integer(key->type))
	{
		RETURN_SEMA_ERROR(key, "The ordinal should be an integer.");
	}
	bool is_const_enum = decl->decl_kind == DECL_CONST_ENUM;
	if (sema_cast_const(key))
	{
		Int to_convert = key->const_expr.ixx;
		if (int_is_neg(to_convert))
		{
			RETURN_SEMA_ERROR(key, "'from_ordinal' doesn't work on negative numbers.");
		}
		unsigned max_enums = vec_size(decl->enums.values);
		Int max = {.i.low = max_enums, .type = TYPE_U32};
		if (int_comp(to_convert, max, BINARYOP_GE))
		{
			RETURN_SEMA_ERROR(key, "The ordinal '%s' exceeds the max ordinal '%u'.", int_to_str(max, 10, false), max_enums - 1);
		}
		if (is_const_enum)
		{
			Expr *val = decl->enums.values[to_convert.i.low]->enum_constant.value;
			*expr = *copy_expr_single(val);
			return true;
		}
		expr->expr_kind = EXPR_CONST;
		expr->const_expr = (ExprConst) {
				.enum_val = decl->enums.values[to_convert.i.low],
				.const_kind = CONST_ENUM
		};
		expr->type = decl->type;
		return true;
	}
	if (is_const_enum)
	{
		RETURN_SEMA_ERROR(key, ".from_ordinal on const enums is only valid with compile time constant arguments, maybe you can try using regular enums?");
	}
	expr->expr_kind = EXPR_ENUM_FROM_ORD;
	expr->inner_expr = key;
	expr->type = decl->type;
	return true;
}

INLINE bool sema_expr_analyse_lookup(SemaContext *context, Expr *expr, Expr *tag, bool inline_field)
{
	Expr **args = expr->call_expr.arguments;
	unsigned arg_count = vec_size(args);
	Decl *decl = tag->type_call_expr.type;
	if (inline_field)
	{
		if (arg_count != 1) RETURN_SEMA_ERROR(expr, "Expected one (1) argument to 'lookup', did you want 'lookup_field' perhaps?");
	}
	else
	{
		if (arg_count != 2) RETURN_SEMA_ERROR(expr, "'lookup_field' requires two arguments: the name of the field and the value to search for.");
	}
	Expr *key = inline_field ? args[0] : args[1];
	if (!sema_analyse_expr_rvalue(context, key)) return false;
	ArrayIndex index;
	if (inline_field)
	{
		if (!decl->is_substruct || decl->enums.inline_value)
		{
			RETURN_SEMA_ERROR(expr, "'lookup' requires an inline associated value, use 'Enum.lookup_field(fieldname, value)' instead.");
		}
	}
	else
	{
		Expr *ident = sema_expr_resolve_access_child(context, args[0], NULL);
		if (!ident) return false;
		const char *child = ident->unresolved_ident_expr.ident;
		FOREACH_IDX(i, Decl *, param, decl->enums.parameters)
		{
			if (param->name && param->name == child)
			{
				index = (ArrayIndex)i;
				goto FOUND;
			}
		}
		RETURN_SEMA_ERROR(args[0], "There is no associated value of %s with the name '%s'.", type_quoted_error_string(decl->type), child);
	}
	index = decl->enums.inline_index;
FOUND:;
	Decl *match = decl->enums.parameters[index];
	if (!cast_implicit(context, key, match->type, false)) return false;
	Decl *d = sema_find_symbol(context, kw_at_enum_lookup);
	if (!d || d->unit->module->name->module != kw_std__core__runtime)
	{
		RETURN_SEMA_ERROR(expr, "Missing main enum lookup macro '%s' in '%s'.", kw_at_enum_lookup, kw_std__core__runtime);
	}
	Expr *type = expr_new_expr(EXPR_TYPEINFO, expr);
	type->type_expr = type_info_new_base(decl->type, tag->span);
	expr->expr_kind = EXPR_CALL;
	while (vec_size(args) < 3) vec_add(args, NULL);
	args[0] = type;

	args[1] = expr_new_const_string(expr->span, match->name);
	args[2] = key;
	Expr *call = expr_new_expr(EXPR_UNRESOLVED_IDENTIFIER, expr);
	Path *new_path = CALLOCS(Path);
	new_path->module = kw_std__core__runtime;
	new_path->span = expr->span;
	new_path->len = strlen(kw_std__core__runtime);
	call->unresolved_ident_expr = (ExprUnresolvedIdentifier) { .ident = kw_at_enum_lookup, .path = new_path };
	expr->call_expr = (ExprCall) { .arguments = args, .function = exprid(call) };
	expr->resolve_status = RESOLVE_NOT_DONE;
	return sema_analyse_expr_rvalue(context, expr);
}

static inline bool sema_expr_analyse_typecall(SemaContext *context, Expr *expr)
{
	Expr *tag = exprptr(expr->call_expr.function);
	expr->call_expr.arguments = sema_expand_vasplat_exprs(context, expr->call_expr.arguments);
	switch (tag->type_call_expr.property)
	{
		case TYPE_PROPERTY_FROM_ORDINAL:
			return sema_expr_analyse_from_ordinal(context, expr, tag);
		case TYPE_PROPERTY_LOOKUP:
			return sema_expr_analyse_lookup(context, expr, tag, true);
		case TYPE_PROPERTY_LOOKUP_FIELD:
			return sema_expr_analyse_lookup(context, expr, tag, false);
		default:
			break;
	}
	Expr **args = expr->call_expr.arguments;
	unsigned arg_count = vec_size(args);
	bool is_has = tag->type_call_expr.property == TYPE_PROPERTY_HAS_TAGOF;
	const char *name = is_has ? "has_tagof" : "tagof";
	if (arg_count != 1) RETURN_SEMA_ERROR(expr, "Expected a single string argument to '%s'.", name);
	Expr *key = args[0];
	if (!sema_analyse_expr_rvalue(context, key)) return false;
	if (!sema_cast_const(key) || !expr_is_const_string(key))
	{
		RETURN_SEMA_ERROR(key, "The tag name should be a string constant.");
	}
	Decl *decl = tag->type_call_expr.type;
	const char *tagname = key->const_expr.bytes.ptr;
	if (!decl) goto NOT_FOUND;
	ASSERT_SPAN(expr, decl->resolved_attributes);
	ResolvedAttrData *attrs = decl->attrs_resolved;
	if (!attrs || !attrs->tags) goto NOT_FOUND;
	Expr *value = NULL;
	FOREACH(Attr *, attr, attrs->tags)
	{
		if (str_eq(attr->exprs[0]->const_expr.bytes.ptr, tagname)) value = attr->exprs[1];
	}
	if (!value) goto NOT_FOUND;
	if (is_has)
	{
		expr_rewrite_const_bool(expr, type_bool, true);
		return true;
	}
	expr_replace(expr, expr_copy(value));
	return true;
NOT_FOUND:
	if (is_has)
	{
		expr_rewrite_const_bool(expr, type_bool, false);
		return true;
	}
	RETURN_SEMA_ERROR(expr, "The tag '%s' is not defined, always check with '.has_tagof'.", tagname);
}

INLINE bool sema_call_may_not_have_attributes(SemaContext *context, Expr *expr)
{
	if (expr->call_expr.attr_force_inline)
	{
		RETURN_SEMA_ERROR(expr, "'@inline' is not allowed here.");
	}
	if (expr->call_expr.attr_force_inline)
	{
		RETURN_SEMA_ERROR(expr, "'@noinline' is not allowed here.");
	}
	if (expr->call_expr.attr_force_inline)
	{
		RETURN_SEMA_ERROR(expr, "'@pure' is not allowed here.");
	}
	return true;
}

INLINE bool sema_analyse_member_get_set_common(SemaContext *context, Decl *decl, Expr *inner, bool *is_bitrstruct)
{
	if (!sema_analyse_expr_rvalue(context, inner)) return false;
	Type *type = type_flatten(inner->type);
	bool type_is_bitstruct = type->type_kind == TYPE_BITSTRUCT;
	if (!type_is_bitstruct && type->type_kind != TYPE_STRUCT && type->type_kind != TYPE_UNION)
	{
		RETURN_SEMA_ERROR(inner, "The member does not belong to the type %s.", type_quoted_error_string(inner->type));
	}
	Decl **members = type->decl->strukt.members;
	ArrayIndex index = -1;
	FOREACH_IDX(i, Decl *, member, members)
	{
		if (member == decl)
		{
			index = (ArrayIndex)i;
			break;
		}
	}
	if (index == -1)
	{
		RETURN_SEMA_ERROR(inner, "The member does not belong to the type %s.", type_quoted_error_string(inner->type));
	}
	*is_bitrstruct = type_is_bitstruct;
	return true;
}

static inline bool sema_call_analyse_member_set(SemaContext *context, Expr *expr)
{
	if (vec_size(expr->call_expr.arguments) != 2)
	{
		RETURN_SEMA_ERROR(expr, "Expected two arguments to '.set'.");
	}
	if (!sema_call_may_not_have_attributes(context, expr)) return false;
	Expr *get = exprptr(expr->call_expr.function);
	Decl *decl = get->member_get_expr;
	Expr *inner = expr->call_expr.arguments[0];
	bool is_bitstruct;
	if (!sema_analyse_member_get_set_common(context, decl, inner, &is_bitstruct)) return false;
	Expr *arg = expr->call_expr.arguments[1];
	Expr *access = expr_new_expr(is_bitstruct ? EXPR_BITACCESS : EXPR_ACCESS_RESOLVED, expr);
	access->access_resolved_expr = (ExprResolvedAccess) { .parent = inner, .ref = decl };
	access->type = decl->type;
	access->resolve_status = RESOLVE_DONE;
	expr->expr_kind = EXPR_BINARY;
	expr->binary_expr = (ExprBinary) { .left =  exprid(access), .right = exprid(arg), .operator = BINARYOP_ASSIGN };
	return sema_expr_analyse_binary(context, NULL, expr, NULL);
}

static inline bool sema_call_analyse_member_get(SemaContext *context, Expr *expr)
{
	if (vec_size(expr->call_expr.arguments) != 1)
	{
		RETURN_SEMA_ERROR(expr, "Expected a single argument to '.get'.");
	}
	if (!sema_call_may_not_have_attributes(context, expr)) return false;
	Expr *get = exprptr(expr->call_expr.function);
	Decl *decl = get->member_get_expr;
	Expr *inner = expr->call_expr.arguments[0];
	bool is_bitstruct;
	if (!sema_analyse_member_get_set_common(context, decl, inner, &is_bitstruct)) return false;
	expr->expr_kind = is_bitstruct ? EXPR_BITACCESS : EXPR_ACCESS_RESOLVED;
	expr->access_resolved_expr = (ExprResolvedAccess) { .parent = inner, .ref = decl };
	expr->type = decl->type;
	return true;
}
static inline bool sema_expr_analyse_call(SemaContext *context, Expr *expr, bool *no_match_ref)
{
	if (no_match_ref) *no_match_ref = true;
	Expr *func_expr = exprptr(expr->call_expr.function);
	if (!sema_analyse_expr(context, func_expr)) return false;
	bool optional = func_expr->type && IS_OPTIONAL(func_expr);
	Decl *decl;
	Expr *struct_var = NULL;
	switch (func_expr->expr_kind)
	{
		case EXPR_MACRO_BODY_EXPANSION:
			return sema_call_analyse_body_expansion(context, expr);
		case EXPR_MEMBER_GET:
			return sema_call_analyse_member_get(context, expr);
		case EXPR_MEMBER_SET:
			return sema_call_analyse_member_set(context, expr);
		case EXPR_TYPECALL:
			return sema_expr_analyse_typecall(context, expr);
		case EXPR_BUILTIN:
			return sema_expr_analyse_builtin_call(context, expr);
		case EXPR_IDENTIFIER:
			decl = func_expr->ident_expr;
			if (!sema_analyse_decl(context, decl)) return false;
			break;
		case EXPR_ACCESS_RESOLVED:
			decl = func_expr->access_resolved_expr.ref;
			if (!sema_analyse_decl(context, decl)) return false;
			switch (decl->decl_kind)
			{
				case DECL_MACRO:
					struct_var = func_expr->access_resolved_expr.parent;
					if (decl->func_decl.signature.params[0]->type->canonical != struct_var->type->canonical
						&& decl->func_decl.signature.params[0]->type->type_kind == TYPE_POINTER)
					{
						expr_insert_addr(struct_var);
					}
					break;
				case DECL_FUNC:
					struct_var = func_expr->access_resolved_expr.parent;
					if (decl->func_decl.signature.params[0]->type->type_kind == TYPE_POINTER )
					{
						if (decl->func_decl.signature.params[0]->type->canonical != struct_var->type->canonical
							&& !decl->func_decl.attr_interface_method)
						{
							expr_insert_addr(struct_var);
						}
					}
					break;
				default:
					break;
			}
			break;
		case EXPR_TYPEINFO:
			if (func_expr->type_expr->resolve_status == RESOLVE_DONE)
			{
				SEMA_ERROR(expr, "A type cannot be followed by (), if you intended a cast, use '(type) expression'.");
			}
			else
			{
				SEMA_ERROR(expr, "A type cannot be followed by (), did you mean to use 'type {}'?");
			}
			return false;
		default:
		{
			Type *type = type_flatten(func_expr->type);
			if (type->type_kind == TYPE_FUNC_PTR)
			{
				decl = NULL;
				break;
			}
			if (no_match_ref)
			{
				*no_match_ref = true;
				return false;
			}
			RETURN_SEMA_ERROR(expr, "This value cannot be invoked, did you accidentally add ()?");
		}
	}
	decl = decl ? decl_flatten(decl) : NULL;
	return sema_expr_analyse_general_call(context, expr, decl, struct_var, optional, no_match_ref);
}

static bool sema_slice_index_is_in_range(SemaContext *context, Type *type, Expr *index_expr, bool end_index,
                                         bool from_end, bool *remove_from_end, bool *missing_ref)
{
	ASSERT_SPAN(index_expr, type == type->canonical);
	if (!sema_cast_const(index_expr)) return true;

	Int index = index_expr->const_expr.ixx;
	if (!int_fits(index, TYPE_I64))
	{
		RETURN_SEMA_ERROR(index_expr, "The index cannot be stored in a 64-signed integer, which isn't supported.");
		return false;
	}
	if (from_end && int_is_neg(index))
	{
		RETURN_SEMA_ERROR(index_expr, "Negative numbers are not allowed when indexing from the end.");
	}
	ArrayIndex idx = (ArrayIndex)index.i.low;
RETRY:;
	switch (type->type_kind)
	{
		case TYPE_POINTER:
			ASSERT_SPAN(index_expr, !from_end);
			return true;
		case TYPE_FLEXIBLE_ARRAY:
			ASSERT_SPAN(index_expr, !from_end);
			break;
		case TYPE_UNTYPED_LIST:
		case TYPE_ARRAY:
		case TYPE_VECTOR:
		{
			ArrayIndex len = (ArrayIndex)type->array.len;
			if (from_end)
			{
				idx = len - idx;
				index_expr->const_expr.ixx.i.low = idx;
				*remove_from_end = true;
			}
			// Checking end can only be done for arrays.
			if (end_index && idx >= len)
			{
				if (missing_ref) return *missing_ref = true, false;
				RETURN_SEMA_ERROR(index_expr, "End index out of bounds, was %lld, exceeding %lld.", (long long)idx, (long long)len);
			}
			if (!end_index && idx >= len)
			{
				if (len == 0)
				{
				if (missing_ref) return *missing_ref = true, false;
					RETURN_SEMA_ERROR(index_expr, "Cannot index into a zero size list.");
				}
				if (missing_ref) return *missing_ref = true, false;
				RETURN_SEMA_ERROR(index_expr, "Index out of bounds, was %lld, exceeding maximum (%lld).", (long long)idx, (long long)len - 1);
			}
			break;
		}
		case TYPE_SLICE:
			// If not from end, just check the negative values.
			if (!from_end) break;
			// From end we can only do sanity checks ^0 is invalid for non-end index. ^-1 and less is invalid for all.
			if (idx == 0 && !end_index)
			{
				if (missing_ref) return *missing_ref = true, false;
				RETURN_SEMA_ERROR(index_expr,
				                  "Array index out of bounds, index from end (%lld) must be greater than zero or it will exceed the max array index.",
				                  (long long) idx);
			}
			return true;
		case TYPE_STRUCT:
		{
			Decl *decl = type->decl;
			ASSERT_SPAN(index_expr, decl->is_substruct);
			type = decl->strukt.members[0]->type->canonical;
			goto RETRY;
		}
		default:
			UNREACHABLE
	}
	if (idx < 0)
	{
		RETURN_SEMA_ERROR(index_expr, "Index out of bounds, using a negative index is only allowed for pointers.");
	}
	return true;
}

static Type *sema_subscript_find_indexable_type_recursively(Type **type, Expr **parent)
{
	while (1)
	{
		Type *inner_type = type_get_indexed_type(*type);
		if (!inner_type && type_is_substruct(*type))
		{
			Expr *embedded_struct = expr_access_inline_member(*parent, (*type)->decl);
			*type = embedded_struct->type->canonical;
			*parent = embedded_struct;
			continue;
		}
		return inner_type;
	}
}

static bool sema_subscript_rewrite_index_const_list(Expr *const_list, ArraySize index, bool from_back, Expr *result)
{
	if (const_list->const_expr.const_kind == CONST_SLICE)
	{
		ASSERT_SPAN(const_list, const_list->const_expr.slice_init);
		return expr_rewrite_to_const_initializer_index(const_list->type, const_list->const_expr.slice_init, result, index, from_back);
	}
	ASSERT_SPAN(const_list, const_list->const_expr.const_kind == CONST_INITIALIZER);
	return expr_rewrite_to_const_initializer_index(const_list->type, const_list->const_expr.initializer, result, index, from_back);
}

/**
 * Find subscript type or overload for subscript.
 */
static Expr *sema_expr_find_subscript_type_or_overload_for_subscript(SemaContext *context, Expr *current_expr,
                                                                     OperatorOverload overload_type,
																	 Type **subscript_type_ptr,
                                                                     Decl **overload_ptr)
{
	Decl *overload = NULL;
	overload = sema_find_untyped_operator(current_expr->type, overload_type, NULL);
	if (overload)
	{
		// Overload for []=
		if (overload_type == OVERLOAD_ELEMENT_SET)
		{
			*overload_ptr = overload;
			ASSERT(vec_size(overload->func_decl.signature.params) == 3);
			*subscript_type_ptr = overload->func_decl.signature.params[2]->type;
			return current_expr;
		}
		// Overload found for [] and &[]
		*overload_ptr = overload;
		ASSERT(overload->func_decl.signature.rtype);
		*subscript_type_ptr = type_infoptr(overload->func_decl.signature.rtype)->type;
		return current_expr;
	}
	// Otherwise, see if we have an indexed type.
	Type *inner_type = type_get_indexed_type(current_expr->type);
	if (inner_type)
	{
		*subscript_type_ptr = inner_type;
		*overload_ptr = NULL;
		return current_expr;
	}
	if (type_is_substruct(current_expr->type))
	{
		Expr *embedded_struct = expr_access_inline_member(current_expr, current_expr->type->decl);
		return sema_expr_find_subscript_type_or_overload_for_subscript(context, embedded_struct, overload_type,
		                                                               subscript_type_ptr,
		                                                               overload_ptr);
	}
	return NULL;
}

static inline bool sema_expr_resolve_subscript_index(SemaContext *context, Expr *expr, Expr *subscripted, Expr *index, Type **current_type_ref, Expr **current_expr_ref, Type **subscript_type_ref, Decl **overload_ref, int64_t *index_ref, bool is_ref, OperatorOverload overload_type, bool* missing_ref)
{
	Decl *overload = NULL;
	Type *subscript_type = NULL;
	Expr *current_expr;
	Type *current_type = subscripted->type->canonical;
	if (current_type == type_untypedlist)
	{
		current_expr = subscripted;
	}
	else
	{
		current_expr = sema_expr_find_subscript_type_or_overload_for_subscript(context,
																			   subscripted,
																			   overload_type,
		                                                                       &subscript_type,
		                                                                       &overload);
		if (!overload && !subscript_type && is_ref)
		{
			// Maybe there is a [] overload?
			if (sema_expr_find_subscript_type_or_overload_for_subscript(context, subscripted, overload_type, &subscript_type,
			                                                            &overload))
			{
				if (missing_ref) return *missing_ref = true, false;
				RETURN_SEMA_ERROR(expr, "A function or macro with '@operator(&[])' is not defined for %s, "
				                        "so you need && to take the address of the temporary.",
				                  type_quoted_error_string(subscripted->type));
			}
		}
		if (!subscript_type)
		{
			if (missing_ref) return *missing_ref = true, false;
			switch (overload_type)
			{
				case OVERLOAD_ELEMENT_REF:
					RETURN_SEMA_ERROR(expr, "Getting a reference to a subscript of %s is not possible.", type_quoted_error_string(subscripted->type));
				case OVERLOAD_ELEMENT_SET:
					RETURN_SEMA_ERROR(expr, "Assigning to a subscript of %s is not possible.", type_quoted_error_string(subscripted->type));
				default:
					RETURN_SEMA_ERROR(expr, "Indexing a value of type %s is not possible.", type_quoted_error_string(subscripted->type));
			}
		}
		if (!overload) current_type = type_flatten(current_expr->type);
	}
	ASSERT(current_type == current_type->canonical);
	Type *index_type = NULL;
	if (overload)
	{
		index_type = overload->func_decl.signature.params[1]->type;
		if (!sema_analyse_inferred_expr(context, index_type, index, NULL))
		{
			expr_poison(index);
			return false;
		}
	}
	else
	{
		if (!sema_analyse_expr_rvalue(context, index))
		{
			expr_poison(index);
			return false;
		}
	}

	// Analyse the index.

	int64_t index_value = -1;
	bool start_from_end = expr->subscript_expr.index.start_from_end;
	if (start_from_end && (current_type->type_kind == TYPE_POINTER || current_type->type_kind == TYPE_FLEXIBLE_ARRAY))
	{
		if (missing_ref) return *missing_ref = true, false;
		RETURN_SEMA_ERROR(index, "Indexing from the end is not allowed for pointers "
		                         "and flexible array members.");
	}
	ArrayIndex size;
	bool check_len = !context->call_env.in_no_eval || current_type == type_untypedlist;
	Expr *len_expr = current_expr->expr_kind == EXPR_CT_IDENT ? current_expr->ct_ident_expr.decl->var.init_expr : current_expr;
	if (sema_cast_const(index)&& expr_is_const_int(index) && (size = sema_len_from_expr(len_expr)) >= 0)
	{
		// 4c. And that it's in range.
		if (int_is_neg(index->const_expr.ixx))
		{
			if (missing_ref) return *missing_ref = true, false;
			RETURN_SEMA_ERROR(index, "The index may not be negative.");
		}
		if (!int_fits(index->const_expr.ixx, TYPE_I64) || size == 0)
		{
			if (!check_len)
			{
				index_value = -1;
				goto SKIP;
			}
			if (missing_ref) return *missing_ref = true, false;
			RETURN_SEMA_ERROR(index, "The index is out of range.", size);
		}
		index_value = int_to_i64(index->const_expr.ixx);
		if (start_from_end)
		{
			index_value = size - index_value;
		}
		if (index_value < 0 || index_value >= size)
		{
			if (!check_len)
			{
				index_value = -1;
				goto SKIP;
			}
			if (missing_ref) return *missing_ref = true, false;
			if (start_from_end)
			{
				RETURN_SEMA_ERROR(index,
				                  size > 1
				                  ? "An index of '%lld' from the end is out of range, a value between 1 and %lld was expected."
				                  : "An index of '%lld' from the end is out of range, a value of %lld was expected.",
				                  (long long) (size - index_value),
				                  (long long) size);
			}
			RETURN_SEMA_ERROR(index,
			                  size > 1
			                  ? "An index of '%lld' is out of range, a value between 0 and %lld was expected."
			                  : "An index of '%lld' is out of range, a value of %lld was expected.",
			                  (long long) index_value,
			                  (long long) size - 1);
		}
	}
SKIP:
	*index_ref = index_value;
	*current_type_ref = current_type;
	*current_expr_ref = current_expr;
	*overload_ref = overload;
	*subscript_type_ref = subscript_type;
	return true;
}

static inline bool sema_expr_analyse_subscript_lvalue(SemaContext *context, Expr *expr, bool *failed_ref)
{
	// Evaluate the expression to index.
	Expr *subscripted = exprptr(expr->subscript_expr.expr);
	switch (subscripted->expr_kind)
	{
		case EXPR_CT_IDENT:
			if (!sema_analyse_expr_lvalue(context, subscripted, NULL)) return false;
			break;
		case EXPR_UNARY:
			subscripted->unary_expr.no_read = true;
			goto DEFAULT;
		case EXPR_SUBSCRIPT:
		{
			Expr *inner = expr_copy(subscripted);
			subscripted->expr_kind = EXPR_UNARY;
			subscripted->unary_expr.operator = UNARYOP_ADDR;
			subscripted->unary_expr.expr = inner;

			inner = expr_copy(subscripted);
			subscripted->expr_kind = EXPR_UNARY;
			subscripted->unary_expr.operator = UNARYOP_DEREF;
			subscripted->unary_expr.expr = inner;
			subscripted->unary_expr.no_read = true;
			FALLTHROUGH;
		}
		default:
DEFAULT:
			if (!sema_analyse_expr_rvalue(context, subscripted)) return false;
			break;
	}

	if (!sema_expr_check_assign(context, expr, NULL)) return false;

	Expr *index = exprptr(expr->subscript_expr.index.expr);

	// 3. Check failability due to value.
	bool optional = IS_OPTIONAL(subscripted);

	Type *current_type;
	Expr *current_expr;
	Decl *overload;
	Type *subscript_type;
	int64_t index_value;
	if (!sema_expr_resolve_subscript_index(context, expr, subscripted, index, &current_type, &current_expr, &subscript_type, &overload, &index_value, false, OVERLOAD_ELEMENT_SET, failed_ref))
	{
		return false;
	}

	// 4. If we are indexing into a complist
	if (current_expr->expr_kind == EXPR_CT_IDENT)
	{
		if (index_value == -1)
		{
			if (failed_ref) goto VALID_FAIL_POISON;
			RETURN_SEMA_ERROR(index, "Assigning to a compile time constant requires a constant index.");
		}
		expr->expr_kind = EXPR_CT_SUBSCRIPT;
		expr->ct_subscript_expr = (ExprCtSubscript) { .var = current_expr->ct_ident_expr.decl, .index = (ArrayIndex)index_value };
		expr->type = NULL;
		return true;
	}

	if (!sema_cast_rvalue(context, subscripted, true)) return false;

	bool start_from_end = expr->subscript_expr.index.start_from_end;
	if (overload)
	{
		if (start_from_end)
		{
			Decl *len = sema_find_untyped_operator(current_expr->type, OVERLOAD_LEN, NULL);
			if (!len)
			{
				if (failed_ref) goto VALID_FAIL_POISON;
				RETURN_SEMA_ERROR(subscripted, "Cannot index '%s' from the end, since there is no 'len' overload.", type_to_error_string(subscripted->type));
			}
			if (!sema_analyse_expr_rvalue(context, current_expr)) return false;
			Decl *temp = decl_new_generated_var(current_expr->type, VARDECL_PARAM, current_expr->span);
			Expr *decl = expr_generate_decl(temp, expr_copy(current_expr));
			expr_rewrite_two(current_expr, decl, expr_variable(temp));
			if (!sema_analyse_expr_rvalue(context, current_expr)) return false;
			Expr *var_for_len = expr_variable(temp);
			Expr *len_expr = expr_new(EXPR_CALL, expr->span);
			if (!sema_insert_method_call(context, len_expr, len, var_for_len, NULL, false)) return false;
			if (!sema_analyse_expr_rvalue(context, len_expr)) return false;
			Expr *index_copy = expr_copy(index);
			if (!sema_analyse_expr_rvalue(context, index_copy)) return false;
			if (!cast_explicit(context, index_copy, len_expr->type)) return false;
			expr_rewrite_to_binary(index, len_expr, index_copy, BINARYOP_SUB);
			index->resolve_status = RESOLVE_NOT_DONE;
			if (!sema_analyse_expr_rvalue(context, index)) return false;
		}
		expr->expr_kind = EXPR_SUBSCRIPT_ASSIGN;
		expr->type = subscript_type;
		expr->subscript_assign_expr.expr = exprid(current_expr);
		expr->subscript_assign_expr.index = exprid(index);
		expr->subscript_assign_expr.method = declid(overload);
		return true;
	}

	// Cast to an appropriate type for index.
	if (!cast_to_index_len(context, index, false)) return false;

	optional |= IS_OPTIONAL(index);
	// Check range
	bool remove_from_back = false;
	if (!sema_slice_index_is_in_range(context, current_type, index, false, start_from_end, &remove_from_back,
	                                  failed_ref))
	{
		return false;
	}
	if (remove_from_back)
	{
		expr->subscript_expr.index.start_from_end = false;
	}

	expr->subscript_expr.expr = exprid(current_expr);
	expr->type = type_add_optional(subscript_type, optional);
	return true;
VALID_FAIL_POISON:
	*failed_ref = true;
	return false;
}

static inline bool sema_expr_analyse_subscript(SemaContext *context, Expr *expr, bool *failed_ref)
{
	ASSERT(expr->expr_kind == EXPR_SUBSCRIPT || expr->expr_kind == EXPR_SUBSCRIPT_ADDR);
	bool is_eval_ref = expr->expr_kind == EXPR_SUBSCRIPT_ADDR;

	// Evaluate the expression to index.
	Expr *subscripted = exprptr(expr->subscript_expr.expr);
	if (!sema_analyse_expr(context, subscripted)) return false;

	// 3. Check failability due to value.
	bool optional = IS_OPTIONAL(subscripted);

	// 2. Evaluate the index.
	Expr *index = exprptr(expr->subscript_expr.index.expr);

	Decl *overload = NULL;
	Type *subscript_type = NULL;
	Expr *current_expr;
	Type *current_type = subscripted->type->canonical;
	int64_t index_value;
	OperatorOverload overload_type = (is_eval_ref || expr->subscript_expr.ref) ? OVERLOAD_ELEMENT_REF : OVERLOAD_ELEMENT_AT;

	if (!sema_expr_resolve_subscript_index(context, expr, subscripted, index, &current_type, &current_expr, &subscript_type, &overload, &index_value, is_eval_ref, overload_type, failed_ref)) return false;

	// 4. If we are indexing into a complist
	if (current_type == type_untypedlist)
	{
		if (is_eval_ref)
		{
			if (failed_ref) return *failed_ref = true, false;
			RETURN_SEMA_ERROR(subscripted, "You need to use && to take the address of a temporary.");
		}
		// 4a. This may either be an initializer list or a CT value
		while (current_expr->expr_kind == EXPR_CT_IDENT) current_expr = current_expr->ct_ident_expr.decl->var.init_expr;

		// 4b. Now we need to check that we actually have a valid type.
		if (index_value < 0)
		{
			if (failed_ref) return *failed_ref = true, false;
			RETURN_SEMA_ERROR(index, "To subscript an untyped list a compile time integer index is needed.");
		}
		expr_replace(expr, current_expr->const_expr.untyped_list[index_value]);
		return true;
	}
	if (!sema_cast_rvalue(context, subscripted, true)) return false;


	bool start_from_end = expr->subscript_expr.index.start_from_end;
	if (overload)
	{
		if (start_from_end)
		{
			Decl *len = sema_find_untyped_operator(current_expr->type, OVERLOAD_LEN, NULL);
			if (!len)
			{
				if (failed_ref) return *failed_ref = true, false;
				RETURN_SEMA_ERROR(subscripted, "Cannot index '%s' from the end, since there is no 'len' overload.", type_to_error_string(subscripted->type));
			}
			if (!sema_analyse_expr_rvalue(context, current_expr)) return false;
			Decl *temp = decl_new_generated_var(current_expr->type, VARDECL_PARAM, current_expr->span);
			Expr *decl = expr_generate_decl(temp, expr_copy(current_expr));
			expr_rewrite_two(current_expr, decl, expr_variable(temp));
			if (!sema_analyse_expr_rvalue(context, current_expr)) return false;
			Expr *var_for_len = expr_variable(temp);
			Expr *len_expr = expr_new(EXPR_CALL, expr->span);
			if (!sema_insert_method_call(context, len_expr, len, var_for_len, NULL, false)) return false;
			if (!sema_analyse_expr_rvalue(context, len_expr)) return false;
			Expr *index_copy = expr_copy(index);
			if (!sema_analyse_expr_rvalue(context, index_copy)) return false;
			if (!cast_explicit(context, index_copy, len_expr->type)) return false;
			expr_rewrite_to_binary(index, len_expr, index_copy, BINARYOP_SUB);
			index->resolve_status = RESOLVE_NOT_DONE;
			if (!sema_analyse_expr_rvalue(context, index)) return false;
		}
		Expr **args = NULL;
		vec_add(args, index);
		return sema_insert_method_call(context, expr, overload, current_expr, args, false);
	}

	// Cast to an appropriate type for index.
	if (!cast_to_index_len(context, index, false)) return false;

	optional |= IS_OPTIONAL(index);
	// Check range
	bool remove_from_back = false;
	if (!sema_slice_index_is_in_range(context, current_type, index, false, start_from_end, &remove_from_back,
	                                  failed_ref))
	{
		return false;
	}
	if (remove_from_back)
	{
		start_from_end = expr->subscript_expr.index.start_from_end = false;
	}

	if (is_eval_ref)
	{
		subscript_type = type_get_ptr(subscript_type);
	}
	else
	{
		if (sema_cast_const(index))
		{
			ASSERT_SPAN(index, expr_is_const_int(index));
			sema_cast_const(current_expr);
			bool is_const_initializer = expr_is_const_initializer(current_expr) || (expr_is_const_slice(current_expr) && current_expr->const_expr.slice_init);
			if (is_const_initializer || expr_is_const_string(current_expr) || expr_is_const_bytes(current_expr))
			{
				if (!int_fits(index->const_expr.ixx, TYPE_U32))
				{
					if (failed_ref) return *failed_ref = true, false;
					RETURN_SEMA_ERROR(index, "Index is out of range.");
				}
				ArraySize idx = index->const_expr.ixx.i.low;
				ArrayIndex len = sema_len_from_const(current_expr);
				if (idx > len || (idx == len && !start_from_end) || (idx == 0 && start_from_end))
				{
					if (failed_ref) return *failed_ref = true, false;
					RETURN_SEMA_ERROR(index, "The index (%s%llu) is out of range, the length is just %llu.",
									  start_from_end ? "^" : "",
									  (unsigned long long)idx,
									  (unsigned long long)len);
				}
				if (!is_const_initializer)
				{
					// Handle bytes / String
					if (start_from_end) idx = len - idx;
					unsigned char c = current_expr->const_expr.bytes.ptr[idx];
					expr_rewrite_const_int(expr, type_char, c);
					expr->type = type_char;
					return true;
				}

				if (sema_subscript_rewrite_index_const_list(current_expr, idx, start_from_end, expr)) return true;
			}
		}
	}
	expr->subscript_expr.expr = exprid(current_expr);
	if (is_eval_ref && type_flatten(subscripted->type)->type_kind == TYPE_POINTER)
	{
		expr->type = type_add_optional(subscripted->type, optional);
	}
	else
	{
		expr->type = type_add_optional(subscript_type, optional);
	}
	return true;
}

static inline bool sema_expr_analyse_pointer_offset(SemaContext *context, Expr *expr)
{
	ASSERT_SPAN(expr, expr->expr_kind == EXPR_POINTER_OFFSET);

	// 1. Evaluate the pointer
	Expr *pointer = exprptr(expr->pointer_offset_expr.ptr);
	if (!sema_analyse_expr_rvalue(context, pointer)) return false;

	// 2. Evaluate the offset.
	Expr *offset = exprptr(expr->pointer_offset_expr.offset);
	if (!sema_analyse_expr_rvalue(context, offset)) return false;
	Type *flat = type_flatten(pointer->type);
	unsigned vec_len = flat->type_kind == TYPE_VECTOR ? flat->array.len : 0;

	if (!cast_implicit_binary(context, offset, vec_len ? type_get_vector(type_isz, vec_len) : type_isz, NULL)) return false;

	// 3. Store optionality
	bool is_optional = IS_OPTIONAL(pointer) || IS_OPTIONAL(offset);

	// 4. Possibly constant fold
	if (!vec_len && sema_cast_const(pointer) && expr_is_const_pointer(pointer) && sema_cast_const(offset))
	{
		ASSERT_SPAN(expr, !is_optional);
		Int mul = { .i.low = type_size(type_flatten(pointer->type)->pointer), .type = offset->const_expr.ixx.type };
		Int offset_val = int_mul(mul, offset->const_expr.ixx);
		Int res = int_add64(offset_val, pointer->const_expr.ptr);
		pointer->const_expr.ptr = res.i.low;
		expr_replace(expr, pointer);
		return true;
	}
	expr->type = type_add_optional(pointer->type, is_optional);
	return true;
}

typedef enum RangeEnv
{
	RANGE_ARRAY,
	RANGE_SLICE,
	RANGE_PTR,
	RANGE_FLEXIBLE,
} RangeEnv;

INLINE bool sema_expr_analyse_range_internal(SemaContext *context, Range *range, ArrayIndex len, RangeEnv env)
{
	Expr *start = exprptr(range->start);
	ASSERT(start);
	Expr *end = exprptrzero(range->end);

	if (!sema_analyse_expr_rvalue(context, start)) return false;
	if (end && !sema_analyse_expr_rvalue(context, end)) return false;

	ArrayIndex lowest = range->is_len ? 0 : -1;
	if (!cast_to_index_len(context, start, false)) return false;
	if (end && !cast_to_index_len(context, end, false)) return false;
	Type *end_type = end ? type_no_optional(end->type) : NULL;
	Type *start_type = type_no_optional(start->type);
	if (end && IS_OPTIONAL(end)) range->is_optional = true;
	if (IS_OPTIONAL(start)) range->is_optional = true;
	if (end && end_type != start_type)
	{
		Type *common = type_find_max_type(start_type, end_type, NULL, NULL);
		if (!common)
		{
			SourceSpan span = start->span;
			span = extend_span_with_token(span, end->span);
			RETURN_SEMA_ERROR_AT(span, "No common type can be found between start and end index.");
		}
		if (!cast_implicit(context, start, common, false) || !cast_implicit(context, end, common, false)) return false;
	}
	// Check range
	if (env != RANGE_ARRAY && env != RANGE_SLICE)
	{
		if (range->start_from_end)
		{
			RETURN_SEMA_ERROR(start, "Indexing from the end is not allowed for pointers or flexible array members.");
		}
		if (!end)
		{
			RETURN_SEMA_ERROR(start, "Omitting end index is not allowed for pointers or flexible array members.");
		}
		if (end && range->end_from_end)
		{
			RETURN_SEMA_ERROR(end, "Indexing from the end is not allowed for pointers or flexible array members.");
		}
	}
	if (end && sema_cast_const(end))
	{
		// Only ArrayIndex sized
		if (!expr_is_valid_index(end))
		{
			RETURN_SEMA_ERROR(end, "The index cannot be stored in a 64-signed integer, which isn't supported.");
		}

		ArrayIndex end_index = int_to_i64(end->const_expr.ixx);

		if (range->end_from_end)
		{
			if (end_index < 0) RETURN_SEMA_ERROR(end, "Negative numbers are not allowed when indexing from the end.");
			// Something like  1 .. ^4 with an unknown length.
			if (len < 0) return true;
			// Otherwise we fold the "from end"
			if (end_index + lowest > len)
			{
				RETURN_SEMA_ERROR(end, "An index may only be negative for pointers (it was: %lld).", len - end_index);
			}
			end_index = len - end_index;
			range->end_from_end = false;
		}
		if (end_index < lowest && env != RANGE_PTR)
		{
			RETURN_SEMA_ERROR(end, "An index may only be negative for pointers (it was: %lld).", end_index);
		}
		// No more analysis
		range->const_end = end_index;
		range->range_type = range->is_len ? RANGE_CONST_LEN : RANGE_CONST_END;
	}
	else if (!end && len > 0)
	{
		range->is_len = false;
		range->const_end = len - 1;
		range->range_type = RANGE_CONST_END;
	}

	if (sema_cast_const(start))
	{
		// Only ArrayIndex sized
		if (!expr_is_valid_index(start))
		{
			RETURN_SEMA_ERROR(end, "The index cannot be stored in a 64-signed integer, which isn't supported.");
		}
		// Only ArrayIndex sized
		ArrayIndex start_index = int_to_i64(start->const_expr.ixx);
		if (range->start_from_end)
		{
			if (start_index < 0) RETURN_SEMA_ERROR(end, "Negative numbers are not allowed when indexing from the end.");
			// Something like  ^1 .. 4 with an unknown length.
			if (len < 0) return true;
			// Otherwise we fold the "from end"
			if (len < start_index)
			{
				RETURN_SEMA_ERROR(start, "An index may only be negative for pointers (it was: %lld).", len - start_index);
			}
			start_index = len - start_index;
			range->start_from_end = false;
		}
		if (start_index < 0 && env != RANGE_PTR)
		{
			RETURN_SEMA_ERROR(start, "An index may only be negative for pointers (it was: %lld).", start_index);
		}
		if (len > -1 && start_index >= len)
		{
			RETURN_SEMA_ERROR(start, "Index out of bounds: the start index was %lld, exceeding the maximum (%lld)",
							  start_index, len - 1);
		}
		if (range->range_type == RANGE_CONST_END)
		{
			ArrayIndex end_index = range->const_end;
			if (end_index - lowest < start_index) RETURN_SEMA_ERROR(start, "The start index (%lld) should not be greater than the end index (%lld).",
														   start_index, end_index);
			range->const_end = end_index + 1 - start_index;
			range->range_type = RANGE_CONST_LEN;
			range->is_len = true;
		}
		if (range->range_type == RANGE_CONST_LEN)
		{
			ArrayIndex end_index = range->const_end;
			range->range_type = RANGE_CONST_RANGE;
			range->start_index = start_index;
			range->len_index = end_index;
		}
	}
	if (len > -1)
	{
		switch (range->range_type)
		{
			case RANGE_CONST_END:
				if (range->const_end >= len)
				{
					RETURN_SEMA_ERROR(end ? end : start, "End index out of bounds, was %d, exceeding max index %d.", range->const_end, len - 1);
				}
				break;
			case RANGE_CONST_LEN:
				if (range->const_end > len)
				{
					RETURN_SEMA_ERROR(end ? end : start, "Length out of bounds, was %d, exceeding max length %d.", range->const_end, len);
				}
				break;
			case RANGE_CONST_RANGE:
				if (range->start_index + range->len_index > len)
				{
					RETURN_SEMA_ERROR(end ? end : start, "End index out of bounds, was %d, exceeding max index %d.", range->start_index + range->len_index - 1, len - 1);
				}
				break;
			default:
				break;
		}
	}
	return true;
}


static inline bool sema_expr_analyse_range(SemaContext *context, Range *range, ArrayIndex len, RangeEnv env)
{
	switch (range->status)
	{
		case RESOLVE_DONE:
			return true;
		case RESOLVE_NOT_DONE:
			range->status = RESOLVE_RUNNING;
			if (!sema_expr_analyse_range_internal(context, range, len, env))
			{
				range->status = RESOLVE_NOT_DONE;
				return false;
			}
			range->status = RESOLVE_DONE;
			return true;
		case RESOLVE_RUNNING:
		{
			SourceSpan span = exprptr(range->start)->span;
			if (range->end) span = extend_span_with_token(span, exprptr(range->end)->span);
			sema_error_at(context, span, "Recursive definition of range.");
			range->status = RESOLVE_NOT_DONE;
			return false;
		}
		default:
			UNREACHABLE
	}
}

static inline bool sema_slice_initializer(SemaContext *context, Expr *expr, Expr *subscripted, Range *range)
{
	ConstInitializer *initializer = subscripted->const_expr.initializer;
	if (!type_is_arraylike(initializer->type))
	{
		RETURN_SEMA_ERROR(expr, "It's not possible to slice an expression of type %s.", type_quoted_error_string(subscripted->type));
	}
	Type *new_type = type_get_slice(type_get_indexed_type(subscripted->type));
	// Turn zero length into an untyped list.
	if (range->len_index == 0)
	{
		expr_rewrite_const_empty_slice(expr, new_type);
		return true;
	}
	bool is_vec = initializer->type->type_kind == TYPE_VECTOR;
	Type *inner_type = is_vec
			? type_get_vector(new_type->array.base, range->len_index)
			: type_get_array(new_type->array.base, range->len_index);
	initializer->type = inner_type;
	switch (initializer->kind)
	{
		case CONST_INIT_ZERO:
			break;
		case CONST_INIT_ARRAY_FULL:
			vec_erase_front(initializer->init_array_full, range->start_index);
			vec_resize(initializer->init_array_full, range->len_index);
			break;
		case CONST_INIT_ARRAY:
		{
			unsigned elements = vec_size(initializer->init_array.elements);
			for (unsigned i = 0; i < elements; i++)
			{
				ConstInitializer *element = initializer->init_array.elements[i];
				ArrayIndex index = element->init_array_value.index;
				if (index < range->start_index || index >= range->start_index + range->len_index)
				{
					vec_erase_at(initializer->init_array.elements, i);
					elements--;
					i--;
				}
				element->init_array_value.index -= range->start_index;
			}
			if (vec_size(initializer->init_array.elements) == 0)
			{
				initializer->kind = CONST_INIT_ZERO;
				break;
			}
			break;
		}
		case CONST_INIT_STRUCT:
		case CONST_INIT_UNION:
		case CONST_INIT_VALUE:
		case CONST_INIT_ARRAY_VALUE:
			UNREACHABLE
	}
	subscripted->const_expr.const_kind = CONST_SLICE;
	expr_replace(expr, subscripted);
	expr->type = new_type;
	return true;
}

static inline bool sema_expr_analyse_slice(SemaContext *context, Expr *expr)
{
	ASSERT_SPAN(expr, expr->expr_kind == EXPR_SLICE);
	Expr *subscripted = exprptr(expr->slice_expr.expr);
	if (!sema_analyse_expr(context, subscripted)) return false;
	bool optional = IS_OPTIONAL(subscripted);
	Type *type = type_flatten(subscripted->type);
	Type *original_type = type_no_optional(subscripted->type);
	RangeEnv env;
	switch (type->type_kind)
	{
		case TYPE_POINTER:
			env = RANGE_PTR;
			break;
		case TYPE_FLEXIBLE_ARRAY:
			env = RANGE_FLEXIBLE;
			break;
		case TYPE_SLICE:
			env = RANGE_SLICE;
			break;
		default:
			env = RANGE_ARRAY;
			break;
	}
	ArrayIndex length = sema_len_from_expr(subscripted);
	Range *range = &expr->slice_expr.range;
	if (!sema_expr_analyse_range(context, range, length, env)) return false;
	if (range->is_optional) optional = true;
	if (sema_cast_const(subscripted) && range->range_type == RANGE_CONST_RANGE)
	{
		switch (subscripted->const_expr.const_kind)
		{
			case CONST_STRING:
			{
				const char *data = str_copy(subscripted->const_expr.bytes.ptr + range->start_index, range->len_index);
				expr_rewrite_const_string(expr, data);
				return true;
			}
			case CONST_BYTES:
			{
				char *data = range->len_index ? malloc_arena(range->len_index) : NULL;
				if (data)
				{
					memcpy(data, subscripted->const_expr.bytes.ptr + range->start_index, range->len_index);
				}
				subscripted->const_expr.bytes.ptr = data;
				subscripted->const_expr.bytes.len = range->len_index;
				if (type->type_kind != TYPE_SLICE)
				{
					Type *index = type_get_indexed_type(type);
					ASSERT(index);
					original_type = type_get_slice(index);
				}
				subscripted->type = original_type;
				expr_replace(expr, subscripted);
				return true;
			}
			case CONST_UNTYPED_LIST:
				ASSERT(!type_is_arraylike(subscripted->type));
				vec_erase_front(subscripted->const_expr.untyped_list, range->start_index);
				vec_resize(subscripted->const_expr.untyped_list, range->len_index);
				expr_replace(expr, subscripted);
				return true;
			case CONST_INITIALIZER:
				return sema_slice_initializer(context, expr, subscripted, range);
			case CONST_SLICE:
				if (!subscripted->const_expr.slice_init)
				{
					ASSERT(range->len_index == 0);
					expr_replace(expr, subscripted);
					return true;
				}
				return sema_slice_initializer(context, expr, subscripted, range);
			case CONST_POINTER:
			case CONST_FLOAT:
			case CONST_INTEGER:
			case CONST_BOOL:
			case CONST_ENUM:
			case CONST_FAULT:
			case CONST_TYPEID:
			case CONST_REF:
			case CONST_MEMBER:
				break;
		}
	}
	Expr *current_expr = subscripted;
	Type *inner_type = sema_subscript_find_indexable_type_recursively(&type, &current_expr);
	if (type == type_voidptr) inner_type = type_char;

	if (!inner_type || !type_is_valid_for_array(inner_type))
	{
		RETURN_SEMA_ERROR(subscripted, "Cannot index %s.", type_quoted_error_string(subscripted->type));
	}
	if (current_expr != subscripted)
	{
		expr->slice_expr.expr = exprid(current_expr);
	}
	// Retain the original type when doing distinct slices.
	Type *result_type = type_get_slice(inner_type);
	Type *original_type_canonical = original_type->canonical;
	if (original_type_canonical->type_kind == TYPE_TYPEDEF && type_base(original_type_canonical) == result_type)
	{
		result_type = original_type;
	}
	expr->type = type_add_optional(result_type, optional);
	return true;
}


/**
 * 1. .A -> It is an enum constant.
 * 2. .foo -> It is a function.
 * 3. .@foo -> It is a macro.
 * 4. .#bar -> It is an identifier to resolve as a member or a function
 * 5. .$eval(...) -> resolve the eval and retry.
 * 6. .$ident -> It is a child to resolve as CT param
 * 7. .$Type -> It is a child to resolve as CT type param
 */
 Expr *sema_expr_resolve_access_child(SemaContext *context, Expr *child, bool *missing)
{
	 SourceSpan span = child->span;
	 bool in_hash = false;
RETRY:
	switch (child->expr_kind)
	{
		case EXPR_HASH_IDENT:
			SEMA_DEPRECATED(child, "Using 'abc.#foo' access style is deprecated. Use 'abc.eval($foo)' instead.");
			if (!sema_expr_fold_hash(context, child)) return NULL;
			in_hash = true;
			goto RETRY;
		case EXPR_OTHER_CONTEXT:
		{
			Expr *inner = child->expr_other_context.inner;
			context = child->expr_other_context.context;
			child = inner;
			goto RETRY;
		}
		case EXPR_IDENTIFIER:
			goto ALREADY_RESOLVED;
		case EXPR_UNRESOLVED_IDENTIFIER:
			// A path is not allowed.
			if (child->unresolved_ident_expr.path) break;
			return child;
		case EXPR_CT_IDENT:
			if (child->resolve_status == RESOLVE_DONE) goto ALREADY_RESOLVED;
			return child;
		case EXPR_TYPEINFO:
			if (child->type_expr->kind == TYPE_INFO_CT_IDENTIFIER) return child;
			break;
		case EXPR_CT_EVAL:
		{
			ASSERT_SPAN(child, child->resolve_status != RESOLVE_DONE);
			// Only report missing if missing var is NULL
			Expr *result = sema_ct_eval_expr(context, false, child->inner_expr, missing == NULL);
			if (!expr_ok(result)) return NULL;
			if (!result)
			{
				if (missing) *missing = true;
				return NULL;
			}
			expr_replace(child, result);
			goto RETRY;
		}
		default:
			break;

	}
	sema_error_at(context, span, "Expected an identifier here.");
	return NULL;
ALREADY_RESOLVED:
	if (in_hash)
	{
		sema_error_at(context, span, "An expression cannot already be resolved when used as '.foo'. "
		                             "One way this might happen is if you pass a '#foo' style "
		                             "parameter that is already assigned a type when declared: 'macro @test(int #foo) { ... }'.");
		return NULL;
	}
	sema_error_at(context, span, "This expression was already resolved to an identifier before it was used.");
	return NULL;
}

static inline bool sema_expr_replace_with_enum_array(SemaContext *context, Expr *enum_array_expr, Decl *enum_decl)
{
	if (!sema_analyse_decl(context, enum_decl)) return false;
	Decl **values = enum_decl->enums.values;
	SourceSpan span = enum_array_expr->span;
	Expr *initializer = expr_new(EXPR_INITIALIZER_LIST, span);
	ArraySize elements = vec_size(values);
	Expr **element_values = elements > 0 ? VECNEW(Expr*, elements) : NULL;
	Type *kind = enum_decl->type;
	ConstKind const_kind = CONST_ENUM;
	for (ArraySize i = 0; i < elements; i++)
	{
		Decl *decl = values[i];
		Expr *expr = expr_new(EXPR_CONST, span);
		expr->const_expr.const_kind = const_kind;
		ASSERT_SPAN(enum_array_expr, enum_decl->resolve_status == RESOLVE_DONE);
		expr->const_expr.enum_val = decl;
		ASSERT_SPAN(enum_array_expr, decl_ok(decl));
		expr->type = kind;
		expr->resolve_status = RESOLVE_DONE;
		vec_add(element_values, expr);
	}
	initializer->initializer_list = element_values;
	enum_array_expr->expr_kind = EXPR_COMPOUND_LITERAL;
	enum_array_expr->expr_compound_literal.initializer = initializer;
	enum_array_expr->expr_compound_literal.type_info = type_info_new_base(type_get_array(kind, elements), span);
	enum_array_expr->resolve_status = RESOLVE_NOT_DONE;
	return sema_analyse_expr_rvalue(context, enum_array_expr);
}

static inline bool sema_expr_replace_with_const_enum_array(SemaContext *context, Expr *enum_array_expr, Decl *enum_decl)
{
	if (!sema_analyse_decl(context, enum_decl)) return false;
	Decl **values = enum_decl->enums.values;
	SourceSpan span = enum_array_expr->span;
	Expr *initializer = expr_new(EXPR_INITIALIZER_LIST, span);
	ArraySize elements = vec_size(values);
	Expr **element_values = elements > 0 ? VECNEW(Expr*, elements) : NULL;
	Type *kind = enum_decl->type;
	for (ArraySize i = 0; i < elements; i++)
	{
		Decl *decl = values[i];
		Expr *expr = copy_expr_single(decl->enum_constant.value);
		vec_add(element_values, expr);
	}
	initializer->initializer_list = element_values;
	enum_array_expr->expr_kind = EXPR_COMPOUND_LITERAL;
	enum_array_expr->expr_compound_literal.initializer = initializer;
	enum_array_expr->expr_compound_literal.type_info = type_info_new_base(type_get_array(kind, elements), span);
	enum_array_expr->resolve_status = RESOLVE_NOT_DONE;
	return sema_analyse_expr_rvalue(context, enum_array_expr);
}

static inline bool sema_expr_replace_with_enum_name_array(SemaContext *context, Expr *enum_array_expr, Decl *enum_decl)
{
	if (!sema_analyse_decl(context, enum_decl)) return false;
	Decl **values = enum_decl->enums.values;
	SourceSpan span = enum_array_expr->span;
	Expr *initializer = expr_new(EXPR_INITIALIZER_LIST, span);
	ArraySize elements = vec_size(values);
	Expr **element_values = elements > 0 ? VECNEW(Expr*, elements) : NULL;
	for (ArraySize i = 0; i < elements; i++)
	{
		Decl *decl = values[i];
		Expr *expr = expr_new(EXPR_CONST, span);
		expr_rewrite_const_string(expr, decl->name);
		vec_add(element_values, expr);
	}
	initializer->initializer_list = element_values;
	enum_array_expr->expr_kind = EXPR_COMPOUND_LITERAL;
	enum_array_expr->expr_compound_literal.initializer = initializer;
	enum_array_expr->expr_compound_literal.type_info = type_info_new_base(type_get_slice(type_string), span);
	enum_array_expr->resolve_status = RESOLVE_NOT_DONE;
	return sema_analyse_expr_rvalue(context, enum_array_expr);
}

static inline bool sema_analyse_macro_func_access(SemaContext *context, Expr *expr, Decl *parent, Expr *identifier, const char *kw, bool *missing_ref)
{
	 if (kw == type_property_list[TYPE_PROPERTY_HAS_TAGOF])
	 {
		 expr->expr_kind = EXPR_TYPECALL;
		 expr->type_call_expr = (ExprTypeCall) { .type = parent, .property = TYPE_PROPERTY_HAS_TAGOF };
		 return true;
	 }
	if (kw == type_property_list[TYPE_PROPERTY_TAGOF])
	{
		expr->expr_kind = EXPR_TYPECALL;
		expr->type_call_expr = (ExprTypeCall) { .type = parent, .property = TYPE_PROPERTY_TAGOF };
		return true;
	}
	if (parent->decl_kind == DECL_MACRO)
	{
		if (missing_ref)
		{
			*missing_ref = true;
			return false;
		}
		RETURN_SEMA_ERROR(identifier, "The property '%s' is not valid on the macro '%s'.", kw, parent->name);
	}
	return sema_expr_analyse_type_access(context, expr, parent->type, identifier, missing_ref);
}

static inline bool sema_expr_analyse_type_access(SemaContext *context, Expr *expr, Type *parent_type, Expr *identifier, bool *missing_ref)
{
	ASSERT_SPAN(expr, identifier->expr_kind == EXPR_UNRESOLVED_IDENTIFIER);
	Type *canonical = parent_type->canonical;
	const char *name = identifier->unresolved_ident_expr.ident;
	bool is_const = identifier->unresolved_ident_expr.is_const;

	if (!is_const)
	{
		TypeProperty property = type_property_by_name(name);
		if (sema_type_property_is_valid_for_type(canonical, property))
		{
			return sema_expr_rewrite_to_type_property(context, expr, canonical, type_property_by_name(name), parent_type);
		}
	}

	if (!type_may_have_sub_elements(canonical))
	{
		Decl *member = sema_resolve_type_method(context, parent_type->canonical, name);
		if (!decl_ok(member)) return false;
		if (!member)
		{
			if (missing_ref) goto MISSING_REF;
			RETURN_SEMA_ERROR(expr, "'%s' does not have a property or method '%s'.", type_to_error_string(parent_type), name);
		}
		expr_resolve_ident(expr, member);
		return true;
	}
	Decl *decl = canonical->decl;
	if (!decl_ok(decl)) return false;
	switch (decl->decl_kind)
	{
		case DECL_ENUM:
		case DECL_CONST_ENUM:
			if (is_const)
			{
				if (!sema_expr_analyse_enum_constant(context, expr, name, decl))
				{
					if (missing_ref) goto MISSING_REF;
					if (!decl_ok(decl)) return false;
					RETURN_SEMA_ERROR(expr, "'%s' has no enumeration value '%s'.", decl->name, name);
					return false;
				}
				return true;
			}
			break;
		case DECL_UNION:
		case DECL_STRUCT:
		case DECL_TYPEDEF:
		case DECL_BITSTRUCT:
		case DECL_INTERFACE:
			break;
		default:
			UNREACHABLE
	}

	Decl *member = sema_decl_stack_find_decl_member(context, decl, name, FIELDS_ONLY);
	if (!decl_ok(member)) return false;
	if (!member)
	{
		member = sema_resolve_type_method(context, decl->type, name);
		if (!decl_ok(member)) return false;
	}
	if (!member)
	{
		if (missing_ref) goto MISSING_REF;
		RETURN_SEMA_ERROR(expr, "No method or inner struct/union '%s.%s' found.", type_to_error_string(decl->type), name);
	}
	if (!member->unit)
	{
		if (!sema_analyse_decl(context, decl)) return false;
	}
	if (member->decl_kind == DECL_VAR || member->decl_kind == DECL_UNION || member->decl_kind == DECL_STRUCT || member->decl_kind == DECL_BITSTRUCT)
	{
		expr->expr_kind = EXPR_CONST;
		expr->resolve_status = RESOLVE_DONE;
		AlignSize align;
		if (!sema_set_abi_alignment(context, decl->type, &align)) return false;
		expr->const_expr = (ExprConst) {
			.member.decl = member,
			.member.align = align,
			.member.offset = decl_find_member_offset(decl, member),
			.const_kind = CONST_MEMBER
		};
		expr->type = type_member;
		return true;
	}
	expr_resolve_ident(expr, member);
	return true;
MISSING_REF:
	*missing_ref = true;
	return false;
}

static inline bool sema_expr_analyse_member_access(SemaContext *context, Expr *expr, Expr *parent, Expr *identifier, bool *missing_ref)
{
	ASSERT_SPAN(expr, identifier->expr_kind == EXPR_UNRESOLVED_IDENTIFIER);

	Decl *decl = parent->const_expr.member.decl;
	const char *name = identifier->unresolved_ident_expr.ident;
	bool is_const = identifier->unresolved_ident_expr.is_const;

	if (is_const)
	{
		if (missing_ref) goto MISSING_REF;
		RETURN_SEMA_ERROR(expr, "There is no member '%s' for %s.", name, type_to_error_string(decl->type));
	}

	if (name == kw_offsetof)
	{
		expr_rewrite_const_int(expr, type_usz, parent->const_expr.member.offset);
		return true;
	}
	if (!sema_analyse_decl(context, decl)) return false;

	TypeProperty type_property = type_property_by_name(name);
	switch (type_property)
	{
		case TYPE_PROPERTY_TAGOF:
		case TYPE_PROPERTY_HAS_TAGOF:
		case TYPE_PROPERTY_FROM_ORDINAL:
		case TYPE_PROPERTY_LOOKUP:
		case TYPE_PROPERTY_LOOKUP_FIELD:
			expr->expr_kind = EXPR_TYPECALL;
			expr->type_call_expr = (ExprTypeCall) { .type = decl, .property = type_property };
			return true;
		case TYPE_PROPERTY_SET:
			expr->expr_kind = EXPR_MEMBER_SET;
			expr->member_get_expr = decl;
			expr->type = type_void;
			return true;
		case TYPE_PROPERTY_GET:
			expr->expr_kind = EXPR_MEMBER_GET;
			expr->member_get_expr = decl;
			expr->type = type_void;
			return true;
		case TYPE_PROPERTY_NONE:
			break;
		case TYPE_PROPERTY_QNAMEOF:
			break;
		case TYPE_PROPERTY_NAMEOF:
			expr_rewrite_const_string(expr, decl->name ? decl->name : "");
			return true;
		case TYPE_PROPERTY_ALIGNOF:
			expr_rewrite_const_int(expr, type_usz,
								   type_min_alignment(parent->const_expr.member.offset,
													  parent->const_expr.member.align));
			return true;
		case TYPE_PROPERTY_MEMBERSOF:
			sema_create_const_membersof(expr, decl->type->canonical, parent->const_expr.member.align, parent->const_expr.member.offset);
			return true;
		case TYPE_PROPERTY_METHODSOF:
		case TYPE_PROPERTY_KINDOF:
		case TYPE_PROPERTY_SIZEOF:
			return sema_expr_rewrite_to_type_property(context, expr, decl->type->canonical, type_property, decl->type->canonical);
		case TYPE_PROPERTY_ELEMENTS:
		case TYPE_PROPERTY_EXTNAMEOF:
		case TYPE_PROPERTY_PARAMS:
		case TYPE_PROPERTY_PARAMSOF:
		case TYPE_PROPERTY_RETURNS:
		case TYPE_PROPERTY_INF:
		case TYPE_PROPERTY_LEN:
		case TYPE_PROPERTY_MAX:
		case TYPE_PROPERTY_MIN:
		case TYPE_PROPERTY_NAN:
		case TYPE_PROPERTY_INNER:
		case TYPE_PROPERTY_NAMES:
		case TYPE_PROPERTY_VALUES:
		case TYPE_PROPERTY_ASSOCIATED:
		case TYPE_PROPERTY_PARENTOF:
		case TYPE_PROPERTY_IS_EQ:
		case TYPE_PROPERTY_IS_ORDERED:
		case TYPE_PROPERTY_IS_SUBSTRUCT:
			break;
	}

	Type *underlying_type = type_flatten(decl->type);

	if (!type_is_union_or_strukt(underlying_type) && underlying_type->type_kind != TYPE_BITSTRUCT)
	{
		if (missing_ref) goto MISSING_REF;
		RETURN_SEMA_ERROR(parent, "No member or property '%s' was found.", name);
	}

	Decl *underlying_type_decl = underlying_type->decl;
	Decl *member = sema_decl_stack_find_decl_member(context, underlying_type_decl, name, METHODS_AND_FIELDS);
	if (!decl_ok(member)) return false;
	if (!member || !(decl_is_struct_type(member) || member->decl_kind == DECL_VAR || member->decl_kind == DECL_BITSTRUCT))
	{
		if (missing_ref) goto MISSING_REF;
		RETURN_SEMA_ERROR(expr, "No member '%s' found.", name);
	}

	ASSERT_SPAN(expr, member->unit);

	expr->expr_kind = EXPR_CONST;
	expr->resolve_status = RESOLVE_DONE;
	expr->const_expr = (ExprConst) {
		.member.decl = member,
		.member.align = parent->const_expr.member.align,
		.member.offset = parent->const_expr.member.offset + decl_find_member_offset(decl, member),
		.const_kind = CONST_MEMBER
	};
	expr->type = type_member;
	return true;
MISSING_REF:
	*missing_ref = true;
	return false;
}


static inline void sema_expr_rewrite_typeid_kind(Expr *expr, Expr *parent)
{
	Module *module = global_context_find_module(kw_std__core__types);
	Decl *type_kind = module ? module_find_symbol(module, kw_typekind) : NULL;
	Type *type_for_kind = type_kind ? type_kind->type : type_char;
	expr->expr_kind = EXPR_TYPEID_INFO;
	expr->typeid_info_expr.parent = exprid(parent);
	expr->typeid_info_expr.kind = TYPEID_INFO_KIND;
	expr->type = type_for_kind;
}


static inline bool sema_create_const_kind(SemaContext *context, Expr *expr, Type *type)
{
	Module *module = global_context_find_module(kw_std__core__types);
	Decl *type_kind = module ? module_find_symbol(module, kw_typekind) : NULL;
	unsigned val = type_get_introspection_kind(type->type_kind);
	if (!type_kind)
	{
		// No TypeKind defined, fallback to char.
		expr_rewrite_const_int(expr, type_char, val);
		return true;
	}
	if (type_kind->resolve_status == RESOLVE_NOT_DONE && !sema_analyse_decl(context, type_kind)) return false;
	Decl **values = type_kind->enums.values;
	ASSERT_SPAN(expr, vec_size(values) > val);
	expr->type = type_kind->type;
	expr->expr_kind = EXPR_CONST;
	expr->resolve_status = RESOLVE_DONE;
	ASSERT_SPAN(expr, type_kind->resolve_status == RESOLVE_DONE);
	expr->const_expr = (ExprConst) {
		.const_kind = CONST_ENUM,
		.enum_val = values[val]
	};
	return true;
}

static inline bool sema_create_const_len(Expr *expr, Type *type, Type *flat)
{
	ASSERT_SPAN(expr, flat == type_flatten(flat) && "Should be flattened already.");

	size_t len;
	if (type->type_kind == TYPE_CONST_ENUM)
	{
		len = vec_size(type->decl->enums.values);
		expr_rewrite_const_int(expr, type_usz, len);
		return true;
	}
	switch (flat->type_kind)
	{
		case TYPE_ARRAY:
		case TYPE_VECTOR:
			len = flat->array.len;
			break;
		case TYPE_ENUM:
			len = vec_size(flat->decl->enums.values);
			break;
		case TYPE_INFERRED_ARRAY:
		case TYPE_FLEXIBLE_ARRAY:
		case TYPE_SLICE:
		default:
			UNREACHABLE
	}
	expr_rewrite_const_int(expr, type_usz, len);
	return true;
}

static inline bool sema_create_const_inner(SemaContext *context, Expr *expr, Type *type)
{
	if (!sema_resolve_type_decl(context, type)) return false;
	Type *inner = NULL;
	switch (type->type_kind)
	{
		case TYPE_POINTER:
			inner = type->pointer;
			break;
		case TYPE_OPTIONAL:
			inner = type->optional;
			break;
		case TYPE_TYPEDEF:
			inner = type->decl->distinct->type->canonical;
			break;
		case TYPE_CONST_ENUM:
		case TYPE_ENUM:
			inner = enum_inner_type(type)->canonical;
			break;
		case TYPE_BITSTRUCT:
			inner = type->decl->strukt.container_type->type->canonical;
			break;
		case TYPE_ARRAY:
		case TYPE_FLEXIBLE_ARRAY:
		case TYPE_SLICE:
		case TYPE_INFERRED_ARRAY:
		case TYPE_INFERRED_VECTOR:
		case TYPE_VECTOR:
			inner = type->array.base;
			break;
		default:
			UNREACHABLE
	}
	expr_rewrite_const_typeid(expr, inner);
	return true;
}

static inline bool sema_create_const_parent(SemaContext *context, Expr *expr, Type *type)
{
	if (!sema_resolve_type_decl(context, type)) return false;
	Type *parent = type_find_parent_type(type->canonical);
	if (!parent) parent = type_void;
	expr_rewrite_const_typeid(expr, parent);
	return true;
}

static inline bool sema_create_const_min(Expr *expr, Type *type, Type *flat)
{
	if (type_is_float(flat))
	{
		expr->expr_kind = EXPR_CONST;
		expr->const_expr.const_kind = CONST_FLOAT;
		expr->type = type;
		expr->resolve_status = RESOLVE_DONE;
		expr->const_expr.fxx.type = flat->type_kind;
		switch (flat->type_kind)
		{
			case TYPE_BF16:
				expr->const_expr.fxx.f = -338953138925153547590470800371487866880.0;
				break;
			case TYPE_F16:
				expr->const_expr.fxx.f = -65504.0;
				break;
			case TYPE_F32:
				expr->const_expr.fxx.f = -FLT_MAX;
				break;
			case TYPE_F64:
				expr->const_expr.fxx.f = -DBL_MAX;
				break;
			case TYPE_F128:
				REMINDER("Float 128 not complete");
				expr->const_expr.fxx.f = -DBL_MAX;
				break;
			default:
				UNREACHABLE
		}
		return true;
	}
	if (type_is_integer(flat))
	{
		expr->expr_kind = EXPR_CONST;
		expr->const_expr.const_kind = CONST_INTEGER;
		expr->const_expr.is_character = false;
		expr->const_expr.is_hex = false;
		expr->type = type;
		expr->resolve_status = RESOLVE_DONE;
		expr->const_expr.ixx.type = flat->type_kind;
		switch (flat->type_kind)
		{
			case TYPE_I8:
				expr->const_expr.ixx.i = (Int128){ 0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFF80 };
				break;
			case TYPE_I16:
				expr->const_expr.ixx.i = (Int128){ 0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFF8000 };
				break;
			case TYPE_I32:
				expr->const_expr.ixx.i = (Int128){ 0xFFFFFFFFFFFFFFFF, 0xFFFFFFFF80000000 };
				break;
			case TYPE_I64:
				expr->const_expr.ixx.i = (Int128){ 0xFFFFFFFFFFFFFFFF, 1ULL << 63 };
				expr->const_expr.is_hex = true;
				break;
			case TYPE_I128:
				expr->const_expr.ixx.i = (Int128){ 1ULL << 63, 0 };
				expr->const_expr.is_hex = true;
				break;
			default:
				expr->const_expr.ixx.i = (Int128){ 0, 0 };
				break;
		}
		return true;
	}
	UNREACHABLE
}

static inline bool sema_create_const_params(Expr *expr, Type *type)
{
	ASSERT_SPAN(expr, type->type_kind == TYPE_FUNC_PTR);
	type = type->pointer;
	Signature *sig = type->function.signature;
	unsigned params = vec_size(sig->params);
	Expr **param_exprs = params ? VECNEW(Expr*, params) : NULL;
	for (unsigned i = 0; i < params; i++)
	{
		Decl *decl = sig->params[i];
		Expr *expr_element = expr_new_const_typeid(expr->span, decl->type->canonical);
		vec_add(param_exprs, expr_element);
	}
	expr_rewrite_const_untyped_list(expr, param_exprs);
	return true;
}

static inline bool sema_create_const_paramsof(Expr *expr, Type *type)
{
	ASSERT_SPAN(expr, type->type_kind == TYPE_FUNC_PTR);
	type = type->pointer;
	Signature *sig = type->function.signature;
	unsigned params = vec_size(sig->params);
	Expr **param_exprs = params ? VECNEW(Expr*, params) : NULL;
	SourceSpan span = expr->span;
	for (unsigned i = 0; i < params; i++)
	{
		Decl *decl = sig->params[i];
		Expr *name_expr = expr_new_const_string(span, decl->name ? decl->name : "");
		Expr *type_expr = expr_new_const_typeid(span, decl->type->canonical);
		Expr **values = NULL;
		vec_add(values, name_expr);
		vec_add(values, type_expr);
		Expr *struct_value = sema_create_struct_from_expressions(type_reflected_param->decl, expr->span, values);
		vec_add(param_exprs, struct_value);
	}
	expr_rewrite_const_untyped_list(expr, param_exprs);
	return true;
}

static inline bool sema_create_const_associated(SemaContext *context, Expr *expr, Type *type)
{
	ASSERT_SPAN(expr, type->type_kind == TYPE_ENUM);
	if (!sema_analyse_decl(context, type->decl)) return false;
	Decl **associated = type->decl->enums.parameters;
	unsigned count = vec_size(associated);
	Expr **associated_exprs = count ? VECNEW(Expr*, count) : NULL;
	for (unsigned i = 0; i < count; i++)
	{
		Decl *decl = associated[i];
		Expr *expr_element = expr_new_const_typeid(expr->span, decl->type->canonical);
		vec_add(associated_exprs, expr_element);
	}
	expr_rewrite_const_untyped_list(expr, associated_exprs);
	return true;
}

static inline void sema_create_const_membersof(Expr *expr, Type *type, AlignSize alignment, AlignSize offset)
{
	Decl **members = NULL;
	assert(type->canonical == type);
	switch (type->type_kind)
	{
		case TYPE_UNION:
		case TYPE_STRUCT:
		case TYPE_BITSTRUCT:
			members = type->decl->strukt.members;
			break;
		default:
			expr_rewrite_const_untyped_list(expr, NULL);
			return;
	}
	unsigned count = vec_size(members);
	Expr **member_exprs = count ? VECNEW(Expr*, count) : NULL;
	for (unsigned i = 0; i < count; i++)
	{
		Decl *decl = members[i];
		Expr *expr_element = expr_new(EXPR_CONST, expr->span);
		expr_element->resolve_status = RESOLVE_DONE;
		expr_element->type = type_member;
		expr_element->const_expr = (ExprConst) {
			.const_kind = CONST_MEMBER,
			.member.decl = decl,
			.member.offset = offset + decl->offset,
			.member.align = alignment
		};
		vec_add(member_exprs, expr_element);
	}
	expr_rewrite_const_untyped_list(expr, member_exprs);
}

static inline Expr *create_method_copy(Decl *method, SourceSpan span)
{
	size_t namestr_len = strlen(method->name);
	const char *namestr = str_copy(method->name, namestr_len);
	Expr *expr_element = expr_new(EXPR_CONST, span);
	expr_element->resolve_status = RESOLVE_DONE;
	expr_element->type = type_string;
	expr_element->const_expr = (ExprConst) {
		.const_kind = CONST_STRING,
		.bytes.ptr = namestr,
		.bytes.len = namestr_len,
	};
	return expr_element;
}

static inline void append_to_method_list(Decl **methods, Expr ***method_exprs_ref, SourceSpan span)
{
	FOREACH(Decl *, method, methods)
	{
		if (method->decl_kind == DECL_FUNC)
		{
			vec_add(*method_exprs_ref, create_method_copy(method, span));
		}
	}
}

static inline void sema_append_interface_methods(Decl *interface, Expr ***method_exprs_ref, SourceSpan span)
{
	append_to_method_list(interface->interface_methods, method_exprs_ref, span);
	FOREACH(TypeInfo *, type_info, interface->interfaces)
	{
		sema_append_interface_methods(type_info->type->decl, method_exprs_ref, span);
	}
}
static inline void append_extension_methods(Type *type, Decl **extensions, Expr ***method_exprs_ref, SourceSpan span)
{
	FOREACH(Decl *, method, extensions)
	{
		if (method->decl_kind == DECL_FUNC && typeget(method->func_decl.type_parent) == type)
		{
			vec_add(*method_exprs_ref, create_method_copy(method, span));
		}
	}
}

static inline void sema_create_const_methodsof(Expr *expr, Type *type)
{
	Expr **method_exprs = NULL;
CONTINUE:
	if (type_is_user_defined(type))
	{
		Decl *decl = type->decl;
		// Interface, prefer interface methods.
		if (decl->decl_kind == DECL_INTERFACE)
		{
			sema_append_interface_methods(decl, &method_exprs, expr->span);
		}
		// Look through natively defined methods.
		Methods *methods = decl->method_table;
		if (methods) append_to_method_list(methods->methods, &method_exprs, expr->span);
	}
	else
	{
		append_extension_methods(type, compiler.context.method_extension_list, &method_exprs, expr->span);
	}
	type = type_find_parent_type(type);
	if (type) goto CONTINUE;
	expr_rewrite_const_untyped_list(expr, method_exprs);
}


static inline bool sema_create_const_max(Expr *expr, Type *type, Type *flat)
{
	if (type_is_integer(flat))
	{
		expr->expr_kind = EXPR_CONST;
		expr->const_expr.const_kind = CONST_INTEGER;
		expr->const_expr.is_character = false;
		expr->type = type;
		expr->const_expr.is_hex = false;
		expr->resolve_status = RESOLVE_DONE;
		expr->const_expr.ixx.type = flat->type_kind;
		switch (flat->type_kind)
		{
			case TYPE_I8:
				expr->const_expr.ixx.i = (Int128){ 0, 0x7F };
				break;
			case TYPE_I16:
				expr->const_expr.ixx.i = (Int128){ 0, 0x7FFF };
				break;
			case TYPE_I32:
				expr->const_expr.ixx.i = (Int128){ 0, 0x7FFFFFFFLL };
				break;
			case TYPE_I64:
				expr->const_expr.ixx.i = (Int128){ 0, 0x7FFFFFFFFFFFFFFFLL };
				expr->const_expr.is_hex = true;
				break;
			case TYPE_I128:
				expr->const_expr.ixx.i = (Int128){ 0x7FFFFFFFFFFFFFFFLL, 0xFFFFFFFFFFFFFFFFLL };
				expr->const_expr.is_hex = true;
				break;
			case TYPE_U8:
				expr->const_expr.ixx.i = (Int128){ 0, 0xFF };
				break;
			case TYPE_U16:
				expr->const_expr.ixx.i = (Int128){ 0, 0xFFFF };
				break;
			case TYPE_U32:
				expr->const_expr.ixx.i = (Int128){ 0, 0xFFFFFFFFLL };
				break;
			case TYPE_U64:
				expr->const_expr.ixx.i = (Int128){ 0, 0xFFFFFFFFFFFFFFFFLL };
				expr->const_expr.is_hex = true;
				break;
			case TYPE_U128:
				expr->const_expr.ixx.i = (Int128){ 0xFFFFFFFFFFFFFFFFLL, 0xFFFFFFFFFFFFFFFFLL };
				expr->const_expr.is_hex = true;
				break;
			default:
				UNREACHABLE
		}
		return true;
	}
	if (type_is_float(flat))
	{
		expr->expr_kind = EXPR_CONST;
		expr->const_expr.const_kind = CONST_FLOAT;
		expr->type = type;
		expr->resolve_status = RESOLVE_DONE;
		expr->const_expr.fxx.type = flat->type_kind;
		switch (flat->type_kind)
		{
			case TYPE_BF16:
				expr->const_expr.fxx.f = 338953138925153547590470800371487866880.0;
				break;
			case TYPE_F16:
				expr->const_expr.fxx.f = 65504.0;
				break;
			case TYPE_F32:
				expr->const_expr.fxx.f = FLT_MAX;
				break;
			case TYPE_F64:
				expr->const_expr.fxx.f = DBL_MAX;
				break;
			case TYPE_F128:
				REMINDER("Float 128 not complete");
				expr->const_expr.fxx.f = DBL_MAX;
				break;
			default:
				UNREACHABLE
		}
		return true;
	}
	UNREACHABLE
}

static bool sema_expr_rewrite_typeid_call(Expr *expr, Expr *typeid, TypeIdInfoKind kind, Type *result_type)
{
	expr->expr_kind = EXPR_TYPEID_INFO;
	expr->typeid_info_expr.parent = exprid(typeid);
	expr->typeid_info_expr.kind = kind;
	expr->type = result_type;
	return true;
}

static bool sema_expr_rewrite_to_typeid_property(SemaContext *context, Expr *expr, Expr *typeid, const char *kw, bool *was_error)
{
	TypeProperty property = type_property_by_name(kw);

	if (sema_cast_const(typeid))
	{
		Type *type = typeid->const_expr.typeid;
		if (type == NULL) return false;
		if (!sema_type_property_is_valid_for_type(type, property)) return false;
		*was_error = !sema_expr_rewrite_to_type_property(context, expr, type, property, type);
		return true;
	}
	switch (property)
	{
		case TYPE_PROPERTY_SIZEOF:
			return sema_expr_rewrite_typeid_call(expr, typeid, TYPEID_INFO_SIZEOF, type_usz);
		case TYPE_PROPERTY_LEN:
			return sema_expr_rewrite_typeid_call(expr, typeid, TYPEID_INFO_LEN, type_usz);
		case TYPE_PROPERTY_INNER:
			return sema_expr_rewrite_typeid_call(expr, typeid, TYPEID_INFO_INNER, type_typeid);
		case TYPE_PROPERTY_KINDOF:
			sema_expr_rewrite_typeid_kind(expr, typeid);
			return true;
		case TYPE_PROPERTY_PARENTOF:
			return sema_expr_rewrite_typeid_call(expr, typeid, TYPEID_INFO_PARENTOF, type_typeid);
		case TYPE_PROPERTY_NAMES:
			return sema_expr_rewrite_typeid_call(expr, typeid, TYPEID_INFO_NAMES, type_get_slice(type_string));
		case TYPE_PROPERTY_ALIGNOF:
		case TYPE_PROPERTY_ASSOCIATED:
		case TYPE_PROPERTY_ELEMENTS:
		case TYPE_PROPERTY_EXTNAMEOF:
		case TYPE_PROPERTY_FROM_ORDINAL:
		case TYPE_PROPERTY_GET:
		case TYPE_PROPERTY_SET:
		case TYPE_PROPERTY_HAS_TAGOF:
		case TYPE_PROPERTY_INF:
		case TYPE_PROPERTY_IS_EQ:
		case TYPE_PROPERTY_IS_ORDERED:
		case TYPE_PROPERTY_IS_SUBSTRUCT:
		case TYPE_PROPERTY_LOOKUP:
		case TYPE_PROPERTY_LOOKUP_FIELD:
		case TYPE_PROPERTY_MAX:
		case TYPE_PROPERTY_MEMBERSOF:
		case TYPE_PROPERTY_METHODSOF:
		case TYPE_PROPERTY_MIN:
		case TYPE_PROPERTY_NAMEOF:
		case TYPE_PROPERTY_NAN:
		case TYPE_PROPERTY_PARAMS:
		case TYPE_PROPERTY_PARAMSOF:
		case TYPE_PROPERTY_QNAMEOF:
		case TYPE_PROPERTY_RETURNS:
		case TYPE_PROPERTY_TAGOF:
		case TYPE_PROPERTY_VALUES:
			// Not supported by dynamic typeid
		case TYPE_PROPERTY_NONE:
			return false;
	}
	UNREACHABLE
	return false;
}

static inline bool sema_expr_fold_to_index(Expr *expr, Expr *parent, SubscriptIndex index_expr)
{
	ConstInitializer *init = parent->const_expr.initializer;
	ConstInitializer *result = INVALID_PTR;
	ASSERT_SPAN(expr, !index_expr.start_from_end);
	Int128 i = exprptr(index_expr.expr)->const_expr.ixx.i;
	if (i.high || i.low > MAX_ARRAYINDEX) return false;
	ArrayIndex index = (ArrayIndex)i.low;
	switch (init->kind)
	{
		case CONST_INIT_ZERO:
			expr_rewrite_to_const_zero(expr, expr->type);
			return true;
		case CONST_INIT_STRUCT:
		case CONST_INIT_UNION:
		case CONST_INIT_VALUE:
		case CONST_INIT_ARRAY_VALUE:
			UNREACHABLE
		case CONST_INIT_ARRAY:
			result = NULL;
			FOREACH(ConstInitializer *, e, init->init_array.elements)
			{
				ArrayIndex idx = e->init_array_value.index;
				if (index != idx) continue;
				result = e->init_array_value.element;
				break;
			}
			if (!result)
			{
				expr_rewrite_to_const_zero(expr, expr->type);
				return true;
			}
			break;
		case CONST_INIT_ARRAY_FULL:
			result = init->init_array_full[index];
			break;
	}
	switch (result->kind)
	{
		case CONST_INIT_ZERO:
			expr_rewrite_to_const_zero(expr, expr->type);
			break;
		case CONST_INIT_ARRAY:
		case CONST_INIT_ARRAY_FULL:
		case CONST_INIT_STRUCT:
		case CONST_INIT_UNION:
			expr->expr_kind = EXPR_CONST;
			expr->const_expr.const_kind = CONST_INITIALIZER;
			expr->const_expr.initializer = result;
			expr->type = type_get_indexed_type(parent->type);
			break;
		case CONST_INIT_ARRAY_VALUE:
			UNREACHABLE
		case CONST_INIT_VALUE:
			expr_replace(expr, result->init_value);
			break;
	}
	return true;
}

static inline bool sema_expr_fold_to_member(Expr *expr, Expr *parent, Decl *member)
{
	ConstInitializer *init = parent->const_expr.initializer;
	ConstInitializer *result;
	switch (init->kind)
	{
		case CONST_INIT_ZERO:
			result = init;
			goto EVAL;
		case CONST_INIT_STRUCT:
		{
			FOREACH_IDX(i, Decl *, other_member, type_flatten(parent->type)->decl->strukt.members)
			{
				if (other_member == member)
				{
					result = init->init_struct[i];
					goto EVAL;
				}
			}
			UNREACHABLE
		}
		case CONST_INIT_UNION:
			if (type_flatten(parent->type)->decl->strukt.members[init->init_union.index] != member) return false;
			result = init->init_union.element;
			goto EVAL;
		case CONST_INIT_VALUE:
		case CONST_INIT_ARRAY:
		case CONST_INIT_ARRAY_FULL:
		case CONST_INIT_ARRAY_VALUE:
			UNREACHABLE
	}
	UNREACHABLE
EVAL:
	switch (result->kind)
	{
		case CONST_INIT_ZERO:
			if (member->type->type_kind == TYPE_FLEXIBLE_ARRAY) return false;
			expr_rewrite_to_const_zero(expr, member->type);
			break;
		case CONST_INIT_STRUCT:
		case CONST_INIT_UNION:
		case CONST_INIT_ARRAY:
		case CONST_INIT_ARRAY_FULL:
			expr->expr_kind = EXPR_CONST;
			expr->const_expr.const_kind = CONST_INITIALIZER;
			expr->const_expr.initializer = result;
			expr->type = member->type;
			break;
		case CONST_INIT_ARRAY_VALUE:
			UNREACHABLE
		case CONST_INIT_VALUE:
			expr_replace(expr, result->init_value);
			break;
	}
	return true;
}


static bool sema_type_property_is_valid_for_type(CanonicalType *original_type, TypeProperty property)
{
	switch (original_type->type_kind)
	{
		case CT_TYPES:
			return false;
		default:
			break;
	}
	CanonicalType *type = type_flatten(original_type);
	switch (property)
	{
		case TYPE_PROPERTY_NONE:
			return false;
		case TYPE_PROPERTY_GET:
		case TYPE_PROPERTY_SET:
			return type == type_member;
		case TYPE_PROPERTY_INF:
		case TYPE_PROPERTY_NAN:
			return type_is_float(type);
		case TYPE_PROPERTY_INNER:
			switch (original_type->type_kind)
			{
				case TYPE_POINTER:
				case TYPE_OPTIONAL:
				case TYPE_TYPEDEF:
				case TYPE_ENUM:
				case TYPE_CONST_ENUM:
				case TYPE_BITSTRUCT:
				case TYPE_ARRAY:
				case TYPE_FLEXIBLE_ARRAY:
				case TYPE_SLICE:
				case TYPE_INFERRED_ARRAY:
				case TYPE_INFERRED_VECTOR:
				case TYPE_VECTOR:
					return true;
				default:
					return false;
			}
		case TYPE_PROPERTY_KINDOF:
		case TYPE_PROPERTY_SIZEOF:
		case TYPE_PROPERTY_ALIGNOF:
		case TYPE_PROPERTY_NAMEOF:
		case TYPE_PROPERTY_QNAMEOF:
		case TYPE_PROPERTY_PARENTOF:
		case TYPE_PROPERTY_IS_ORDERED:
		case TYPE_PROPERTY_IS_EQ:
			return true;
		case TYPE_PROPERTY_IS_SUBSTRUCT:
			return type->type_kind == TYPE_STRUCT;
		case TYPE_PROPERTY_LEN:
			if (original_type->type_kind == TYPE_CONST_ENUM) return true;
			switch (type->type_kind)
			{
				case TYPE_ARRAY:
				case TYPE_VECTOR:
				case TYPE_ENUM:
					return true;
				default:
					return false;
			}
		case TYPE_PROPERTY_LOOKUP:
		case TYPE_PROPERTY_LOOKUP_FIELD:
			return type->type_kind == TYPE_ENUM;
		case TYPE_PROPERTY_MIN:
		case TYPE_PROPERTY_MAX:
			return type_is_float(type) || type_is_integer(type);
		case TYPE_PROPERTY_FROM_ORDINAL:
		case TYPE_PROPERTY_NAMES:
		case TYPE_PROPERTY_VALUES:
			return type->type_kind == TYPE_ENUM || original_type->canonical->type_kind == TYPE_CONST_ENUM;
		case TYPE_PROPERTY_ELEMENTS:
		case TYPE_PROPERTY_ASSOCIATED:
			return type->type_kind == TYPE_ENUM;
		case TYPE_PROPERTY_MEMBERSOF:
			switch (type->type_kind)
			{
				case TYPE_STRUCT:
				case TYPE_UNION:
				case TYPE_BITSTRUCT:
					return true;
				default:
					return false;
			}
		case TYPE_PROPERTY_METHODSOF:
			switch (type->type_kind)
			{
				case TYPE_POISONED:
				case TYPE_VOID:
				case TYPE_FUNC_RAW:
				case TYPE_ALIAS:
				case TYPE_UNTYPED_LIST:
				case TYPE_FLEXIBLE_ARRAY:
				case TYPE_OPTIONAL:
				case TYPE_WILDCARD:
				case TYPE_TYPEINFO:
				case TYPE_MEMBER:
					return false;
				default:
					return true;
			}
		case TYPE_PROPERTY_PARAMSOF:
		case TYPE_PROPERTY_PARAMS:
		case TYPE_PROPERTY_RETURNS:
			return type_is_func_ptr(type);
		case TYPE_PROPERTY_TAGOF:
		case TYPE_PROPERTY_HAS_TAGOF:
			return true;
		case TYPE_PROPERTY_EXTNAMEOF:
			return !type_is_builtin(original_type->canonical->type_kind);
	}
	UNREACHABLE
}

static bool sema_expr_rewrite_to_type_property(SemaContext *context, Expr *expr, Type *type, TypeProperty property,
											   Type *parent_type)
{
	ASSERT_SPAN(expr, type == type->canonical);
	ASSERT_SPAN(expr, sema_type_property_is_valid_for_type(type, property));
	Type *flat = type_flatten(type);
	switch (property)
	{
		case TYPE_PROPERTY_INF:
			ASSERT_SPAN(expr, type_is_float(flat));
			expr->expr_kind = EXPR_CONST;
			expr->const_expr.const_kind = CONST_FLOAT;
			expr->const_expr.fxx = (Float) { INFINITY, flat->type_kind };
			expr->type = type;
			expr->resolve_status = RESOLVE_DONE;
			return true;
		case TYPE_PROPERTY_IS_ORDERED:
			expr_rewrite_const_bool(expr, type_bool, type_is_ordered(flat));
			return true;
		case TYPE_PROPERTY_IS_EQ:
			switch (sema_type_can_check_equality_with_overload(context, flat))
			{
				case BOOL_ERR:
					return false;
				case BOOL_TRUE:
					expr_rewrite_const_bool(expr, type_bool, true);
					return true;
				case BOOL_FALSE:
					expr_rewrite_const_bool(expr, type_bool, false);
					return true;
			}
			UNREACHABLE
		case TYPE_PROPERTY_IS_SUBSTRUCT:
			expr_rewrite_const_bool(expr, type_bool, type_is_substruct(flat));
			return true;
		case TYPE_PROPERTY_INNER:
			return sema_create_const_inner(context, expr, type);
		case TYPE_PROPERTY_PARENTOF:
			return sema_create_const_parent(context, expr, type);
		case TYPE_PROPERTY_KINDOF:
			return sema_create_const_kind(context, expr, type);
		case TYPE_PROPERTY_LEN:
			return sema_create_const_len(expr, type, flat);
		case TYPE_PROPERTY_MIN:
			return sema_create_const_min(expr, type, flat);
		case TYPE_PROPERTY_MAX:
			return sema_create_const_max(expr, type, flat);
		case TYPE_PROPERTY_NAMES:
			if (type->type_kind == TYPE_CONST_ENUM)
			{
				return sema_expr_replace_with_enum_name_array(context, expr, type->decl);
			}
			ASSERT_SPAN(expr, flat->type_kind == TYPE_ENUM);
			return sema_expr_replace_with_enum_name_array(context, expr, flat->decl);
		case TYPE_PROPERTY_ASSOCIATED:
			return sema_create_const_associated(context, expr, flat);
		case TYPE_PROPERTY_ELEMENTS:
			ASSERT_SPAN(expr, flat->type_kind == TYPE_ENUM);
			if (!sema_analyse_decl(context, type->decl)) return false;
			SEMA_DEPRECATED(expr, ".elements is deprecated. Use .values.len instead.");
			expr_rewrite_const_int(expr, type_isz, vec_size(flat->decl->enums.values));
			return true;
		case TYPE_PROPERTY_VALUES:
			if (type->type_kind == TYPE_CONST_ENUM)
			{
				return sema_expr_replace_with_const_enum_array(context, expr, type->decl);
			}
			ASSERT_SPAN(expr, flat->type_kind == TYPE_ENUM);
			return sema_expr_replace_with_enum_array(context, expr, flat->decl);
		case TYPE_PROPERTY_NAN:
			ASSERT_SPAN(expr, type_is_float(flat));
			expr->expr_kind = EXPR_CONST;
			expr->const_expr.const_kind = CONST_FLOAT;
			expr->const_expr.fxx = (Float) { nan(""), flat->type_kind };
			expr->type = type;
			expr->resolve_status = RESOLVE_DONE;
			return true;
		case TYPE_PROPERTY_GET:
		case TYPE_PROPERTY_SET:
			UNREACHABLE
		case TYPE_PROPERTY_MEMBERSOF:
		{
			AlignSize align;
			if (!sema_set_abi_alignment(context, parent_type, &align)) return false;
			sema_create_const_membersof(expr, flat, align, 0);
			return true;
		}
		case TYPE_PROPERTY_METHODSOF:
			sema_create_const_methodsof(expr, type);
			return true;
		case TYPE_PROPERTY_PARAMSOF:
			return sema_create_const_paramsof(expr, flat);
		case TYPE_PROPERTY_PARAMS:
			SEMA_DEPRECATED(expr, "'params' is deprecated, use 'paramsof' instead.");
			return sema_create_const_params(expr, flat);
		case TYPE_PROPERTY_RETURNS:
			expr_rewrite_const_typeid(expr, type_infoptr(flat->pointer->function.signature->rtype)->type);
			return true;
		case TYPE_PROPERTY_SIZEOF:
			if (!sema_resolve_type_decl(context, type)) return false;
			expr_rewrite_const_int(expr, type_usz, type_size(type));
			return true;
		case TYPE_PROPERTY_NAMEOF:
			if (!sema_resolve_type_decl(context, type)) return false;
			sema_expr_rewrite_to_type_nameof(expr, type, TOKEN_CT_NAMEOF);
			return true;
		case TYPE_PROPERTY_QNAMEOF:
			if (!sema_resolve_type_decl(context, type)) return false;
			sema_expr_rewrite_to_type_nameof(expr, type, TOKEN_CT_QNAMEOF);
			return true;
		case TYPE_PROPERTY_ALIGNOF:
		{
			AlignSize align;
			if (!sema_set_abi_alignment(context, type, &align)) return false;
			expr_rewrite_const_int(expr, type_usz, align);
			return true;
		}
		case TYPE_PROPERTY_EXTNAMEOF:
			ASSERT_SPAN(expr, !type_is_builtin(type->type_kind));
			if (!sema_resolve_type_decl(context, type)) return false;
			sema_expr_rewrite_to_type_nameof(expr, type, TOKEN_CT_EXTNAMEOF);
			return true;
		case TYPE_PROPERTY_TAGOF:
			if (!type_is_user_defined(type))
			{
				RETURN_SEMA_ERROR(expr, "'tagof' is not defined for builtin types like %s.", type_quoted_error_string(type));
			}
			FALLTHROUGH;
		case TYPE_PROPERTY_HAS_TAGOF:
			if (!type_is_user_defined(type))
			{

				expr->expr_kind = EXPR_TYPECALL;
				expr->type_call_expr = (ExprTypeCall) {
					.type = NULL,
					.property = property };
				return true;
			}
			goto TYPE_CALL;
		case TYPE_PROPERTY_LOOKUP:
			if (!compiler.build.old_enums)
			{
				SEMA_DEPRECATED(expr, ".lookup is deprecated.");
			}
			goto TYPE_CALL;
		case TYPE_PROPERTY_FROM_ORDINAL:
		case TYPE_PROPERTY_LOOKUP_FIELD:
			goto TYPE_CALL;
		case TYPE_PROPERTY_NONE:
			return false;
	}
	UNREACHABLE
TYPE_CALL:
	expr->expr_kind = EXPR_TYPECALL;
	expr->type_call_expr = (ExprTypeCall) {
		.type = type->type_kind == TYPE_FUNC_PTR
				? type->pointer->function.decl
				: type->decl,
		.property = property };
	return true;
}


bool sema_expr_rewrite_insert_deref(SemaContext *context, Expr *original)
{
	if (expr_is_const_pointer(original) && !original->const_expr.ptr)
	{
		RETURN_SEMA_ERROR(original, "This value is known to be null so you cannot dereference it.");
	}
	// Assume *(&x) => x
	if (original->expr_kind == EXPR_UNARY && original->unary_expr.operator == UNARYOP_ADDR)
	{
		*original = *original->unary_expr.expr;
		return true;
	}

	// Allocate our new and create our new inner, and overwrite the original.
	Expr *inner = expr_copy(original);
	original->expr_kind = EXPR_UNARY;
	original->type = NULL;
	original->unary_expr.operator = UNARYOP_DEREF;
	original->unary_expr.expr = inner;

	// In the case the original is already resolved, we want to resolve the deref as well.
	if (original->resolve_status == RESOLVE_DONE)
	{
		Type *no_fail  = type_no_optional(inner->type);
		ASSERT(no_fail->canonical->type_kind == TYPE_POINTER);

		// Only fold to the canonical type if it wasn't a pointer.
		Type *pointee = no_fail->type_kind == TYPE_POINTER ? no_fail->pointer : no_fail->canonical->pointer;
		original->type = type_add_optional(pointee, IS_OPTIONAL(inner));
	}
	return true;
}

static inline bool sema_expr_analyse_swizzle(SemaContext *context, Expr *expr, Expr *parent, Type *flat_type,
                                             const char *kw, unsigned len)
{
	unsigned vec_len = flat_type->array.len;
	Type *indexed_type = type_get_indexed_type(parent->type);
	assert(indexed_type);
	bool is_ref = expr->access_unresolved_expr.is_ref;
	bool is_lvalue = expr->access_unresolved_expr.is_lvalue;
	if (is_lvalue) is_ref = false;
	ASSERT_SPAN(expr, len > 0);
	int index = 0;
	bool is_overlapping = false;
	for (unsigned i = 0; i < len; i++)
	{
		char val = (char)(swizzle[(int)kw[i]] - 1);
		if ((val & 0xF) >= vec_len)
		{
			RETURN_SEMA_ERROR(expr, "The '%c' component is not present in a vector of length %d, did you assume a longer vector?", kw[i], vec_len);
		}
		if (i == 0)
		{
			index = (int)val;
		}
		if ((index ^ val) & 0x10)
		{
			RETURN_SEMA_ERROR(expr, "Mixing [xyzw] and [rgba] is not permitted, you will need to select one of them.");
		}
		if (!is_overlapping)
		{
			for (int j = 0; j < i; j++)
			{
				char prev = (char)(swizzle[(int)kw[j]] - 1);
				if (val == prev)
				{
					is_overlapping = true;
					break;
				}
			}
		}
	}
	index &= 0xF;
	if (len == 1)
	{
		expr->expr_kind = is_ref ? EXPR_SUBSCRIPT_ADDR : EXPR_SUBSCRIPT;
		expr->subscript_expr = (ExprSubscript) {
				.index.expr = exprid(expr_new_const_int(expr->span, type_usz, index)),
				.expr = exprid(parent),
				.ref = is_ref
		};
		if (is_lvalue)
		{
			if (!sema_expr_analyse_subscript_lvalue(context, expr, NULL)) return false;
		}
		else
		{
			if (!sema_expr_analyse_subscript(context, expr, NULL)) return false;
		}
		expr->resolve_status = RESOLVE_DONE;
		if (is_ref)
		{
			if (!sema_expr_rewrite_insert_deref(context, expr)) return false;
		}
		return true;
	}
	Type *result = type_get_vector(indexed_type, len);
	expr->expr_kind = EXPR_SWIZZLE;
	expr->swizzle_expr = (ExprSwizzle) { .parent = exprid(parent), .swizzle = kw, .is_overlapping = is_overlapping };

	expr->type = result;
	return true;
}

static inline bool sema_analyse_maybe_dead_expr(SemaContext *context, Expr *expr, bool is_dead, Type *infer_type)
{
	if (!is_dead || context->active_scope.is_dead)
	{
		return infer_type ? sema_analyse_inferred_expr(context, infer_type, expr, NULL) : sema_analyse_expr_rvalue(context, expr);
	}
	context->active_scope.is_dead = true;
	bool success = infer_type ? sema_analyse_inferred_expr(context, infer_type, expr, NULL) : sema_analyse_expr_rvalue(context, expr);
	context->active_scope.is_dead = false;
	return success;
}

static inline void sema_expr_flatten_const_ident(Expr *expr)
{
	if (expr->expr_kind != EXPR_IDENTIFIER) return;
	Decl *ident = expr->ident_expr;
	if (ident->decl_kind != DECL_VAR) return;
	switch (ident->var.kind)
	{
		case VARDECL_CONST:
		case VARDECL_LOCAL_CT:
		case VARDECL_PARAM_CT:
			break;
		case VARDECL_GLOBAL:
		case VARDECL_LOCAL:
		case VARDECL_PARAM:
		case VARDECL_MEMBER:
		case VARDECL_BITMEMBER:
		case VARDECL_PARAM_EXPR:
		case VARDECL_UNWRAPPED:
		case VARDECL_ERASE:
		case VARDECL_REWRAPPED:
		case VARDECL_PARAM_CT_TYPE:
		case VARDECL_LOCAL_CT_TYPE:
			return;
	}
	Expr *init_expr = ident->var.init_expr;
	if (!init_expr) return;
	sema_expr_flatten_const_ident(init_expr);
	if (expr_is_const(init_expr))
	{
		expr_replace(expr, expr_copy(init_expr));
	}
}

/**
 * Analyse "x.y"
 */
static inline bool sema_expr_analyse_access(SemaContext *context, Expr *expr, bool *missing_ref)
{
	Expr *parent = expr->access_unresolved_expr.parent;
	if (missing_ref) *missing_ref = false;

	if (expr->access_unresolved_expr.is_ref)
	{
		expr_set_to_ref(parent);
	}
	// 1. Resolve the left hand
	if (!sema_analyse_expr(context, parent)) return false;

	// 2. The right hand side may be a @ident or ident
	Expr *child = expr->access_unresolved_expr.child;

	// 3. Handle xxxxxx.typeid
	if (child->expr_kind == EXPR_TYPEINFO)
	{
		if (child->type_expr->resolve_status != RESOLVE_DONE || child->type_expr->type != type_typeid)
		{
			RETURN_SEMA_ERROR(child, "A type can't appear here.");
		}
		if (parent->expr_kind == EXPR_IDENTIFIER && parent->type->type_kind == TYPE_FUNC_RAW)
		{
			expr->type = type_typeid;
			expr->expr_kind = EXPR_CONST;
			expr->const_expr.const_kind = CONST_TYPEID;
			expr->const_expr.typeid = parent->type;
			expr->resolve_status = RESOLVE_DONE;
			return true;
		}
		if (parent->expr_kind == EXPR_TYPEINFO)
		{
			Type *type = parent->type_expr->type->canonical;
			switch (type->type_kind)
			{
				case CT_TYPES:
					RETURN_SEMA_ERROR(parent, "You cannot take the typeid of a compile time type.");
				default:
					expr->type = type_typeid;
					expr->expr_kind = EXPR_CONST;
					expr->const_expr.const_kind = CONST_TYPEID;
					expr->const_expr.typeid = parent->type_expr->type->canonical;
					expr->resolve_status = RESOLVE_DONE;
					return true;
			}
			UNREACHABLE
		}
		if (expr_is_const_member(parent))
		{
			expr->type = type_typeid;
			expr->expr_kind = EXPR_CONST;
			expr->const_expr.const_kind = CONST_TYPEID;
			expr->const_expr.typeid = parent->const_expr.member.decl->type->canonical;
			expr->resolve_status = RESOLVE_DONE;
			return true;
		}
		SEMA_ERROR(expr, "'typeid' can only be used with types, not values");
		return false;
	}

	// 3. Find the actual token.
	Expr *identifier = sema_expr_resolve_access_child(context, child, missing_ref);
	if (!identifier) return false;
	const char *kw = identifier->unresolved_ident_expr.ident;

	// 2. If our left-hand side is a type, e.g. MyInt.abc, handle this here.
	if (parent->expr_kind == EXPR_TYPEINFO)
	{
		return sema_expr_analyse_type_access(context, expr, parent->type_expr->type, identifier, missing_ref);
	}
	if (parent->expr_kind == EXPR_IDENTIFIER || expr_is_const_ref(parent))
	{
		Decl *decl = parent->expr_kind == EXPR_IDENTIFIER ? parent->ident_expr : parent->const_expr.global_ref;
		switch (decl->decl_kind)
		{
			case DECL_FUNC:
			case DECL_MACRO:
				return sema_analyse_macro_func_access(context, expr, decl, identifier, kw, missing_ref);
			default:
				break;
		}
		if (parent->type->type_kind == TYPE_FUNC_RAW)
		{
			return sema_expr_analyse_type_access(context, expr, parent->type, identifier, missing_ref);
		}
	}
	if (expr_is_const_member(parent))
	{
		return sema_expr_analyse_member_access(context, expr, parent, identifier, missing_ref);
	}

	// 6. Copy failability
	bool optional = IS_OPTIONAL(parent);

	ASSERT_SPAN(expr, expr->expr_kind == EXPR_ACCESS_UNRESOLVED);
	ASSERT_SPAN(expr, parent->resolve_status == RESOLVE_DONE);

	// 7. Is this a pointer? If so we insert a deref.
	Type *underlying_type = type_no_optional(parent->type)->canonical;
	if (underlying_type->type_kind == TYPE_POINTER && underlying_type != type_voidptr)
	{
		if (!sema_cast_rvalue(context, parent, true)) return false;
		if (!sema_expr_rewrite_insert_deref(context, expr->access_unresolved_expr.parent)) return false;
		parent = expr->access_unresolved_expr.parent;
	}

	// 8. Depending on parent type, we have some hard coded types
	Expr *current_parent = parent;

	Type *type = type_no_optional(parent->type)->canonical;
	Type *flat_type = type_flatten(type);
	if (kw_type == kw)
	{
		if (type_is_any_raw(flat_type))
		{
			expr_rewrite_to_builtin_access(expr, parent, ACCESS_TYPEOFANY, type_typeid);
			return true;
		}
		if (flat_type->type_kind == TYPE_ANYFAULT)
		{
			expr_rewrite_to_builtin_access(expr, parent, ACCESS_TYPEOFANYFAULT, type_typeid);
			return true;
		}
	}

CHECK_DEEPER:

	// 9. Fix hard coded function `len` on slices and arrays
	if (kw == kw_len)
	{
		ArrayIndex index = sema_len_from_expr(current_parent);
		if (index > -1)
		{
			expr_rewrite_const_int(expr, type_isz, index);
			return true;
		}
		if (flat_type->type_kind == TYPE_SLICE)
		{
			expr_rewrite_slice_len(expr, current_parent, type_usz);
			return true;
		}
		assert(flat_type->type_kind != TYPE_ARRAY && flat_type->type_kind != TYPE_VECTOR);
	}
	if (flat_type->type_kind == TYPE_TYPEID)
	{
		bool was_error = false;
		if (sema_expr_rewrite_to_typeid_property(context, expr, current_parent, kw, &was_error)) return !was_error;
	}
	if (flat_type->type_kind == TYPE_VECTOR)
	{
		unsigned len = strlen(kw);
		if (len <= 4)
		{
			for (unsigned i = 0; i < len; i++)
			{
				if (!swizzle[(int)kw[i]]) goto NOT_SWIZZLE;
			}
			// TODO should we do a missing for this as well?
			return sema_expr_analyse_swizzle(context, expr, current_parent, flat_type, kw, len);
			NOT_SWIZZLE:;
		}
	}
	// Hard coded ptr on slices and any
	if (kw == kw_ptr)
	{
		if (flat_type->type_kind == TYPE_SLICE)
		{
			expr_rewrite_ptr_access(expr, current_parent, type_get_ptr(flat_type->array.base));
			return true;
		}
		if (type_is_any_raw(flat_type))
		{
			expr_rewrite_ptr_access(expr, current_parent, type_voidptr);
			return true;
		}
	}
	if (kw == kw_ordinal && flat_type->type_kind == TYPE_ENUM)
	{
		sema_expr_convert_enum_to_int(current_parent);
		expr_replace(expr, current_parent);
		return true;
	}
	if (kw == kw_nameof)
	{
		if (flat_type->type_kind == TYPE_CONST_ENUM)
		{
			if (sema_cast_const(current_parent))
			{
				expr_rewrite_const_string(expr, current_parent->const_expr.enum_val->name);
				return true;
			}
		}
		if (flat_type->type_kind == TYPE_ENUM)
		{
			if (sema_cast_const(current_parent))
			{
				expr_rewrite_const_string(expr, current_parent->const_expr.enum_val->name);
				return true;
			}
			expr_rewrite_to_builtin_access(expr, current_parent, ACCESS_ENUMNAME, type_string);
			return true;
		}
		if (flat_type == type_fault)
		{
			if (sema_cast_const(current_parent))
			{
				expr_rewrite_const_string(expr, current_parent->const_expr.fault ? current_parent->const_expr.fault->name : "null");
				return true;
			}
			expr_rewrite_to_builtin_access(expr, current_parent, ACCESS_FAULTNAME, type_string);
			return true;
		}
	}

	// 9. At this point we may only have distinct, struct, union, error, enum, interface
	if (!type_may_have_sub_elements(type))
	{
		Decl *method = sema_resolve_type_method(context, type, kw);
		if (!method)
		{
			if (missing_ref) goto MISSING_REF;
			RETURN_SEMA_ERROR(expr, "There is no member or method '%s' on '%s'", kw, type_to_error_string(parent->type));
		}

		ASSERT_SPAN(expr, expr->expr_kind == EXPR_ACCESS_UNRESOLVED);
		expr->type = method->type ? type_add_optional(method->type, optional) : NULL;
		expr->access_resolved_expr = (ExprResolvedAccess) { .parent = current_parent, .ref = method };
		expr->expr_kind = EXPR_ACCESS_RESOLVED;
		if (method->decl_kind == DECL_FUNC) unit_register_external_symbol(context, method);
		return true;
	}

	// 10. Dump all members and methods into a decl stack.
	Decl *decl = type->decl;

	Decl *member = sema_decl_stack_find_decl_member(context, decl, kw, METHODS_INTERFACES_AND_FIELDS);
	if (!decl_ok(member)) return false;

	if (member && decl->decl_kind == DECL_ENUM && member->decl_kind == DECL_VAR && sema_cast_const(parent))
	{
		if (!sema_analyse_decl(context, decl)) return false;
		Decl *ref = current_parent->const_expr.enum_val;
		if (!sema_analyse_decl(context, ref)) return false;
		ASSERT_SPAN(expr, current_parent->const_expr.const_kind == CONST_ENUM);
		Expr *copy_init = copy_expr_single(ref->enum_constant.associated[member->var.index]);
		expr_replace(expr, copy_init);
		ASSERT_SPAN(expr, copy_init->resolve_status == RESOLVE_DONE);
		return true;
	}
	Decl *private = NULL;
	if (!member && decl->decl_kind == DECL_INTERFACE)
	{
		Decl *inf;
		FOREACH(TypeInfo *, parent_interface, decl->interfaces)
		{
			if (!sema_resolve_type_info(context, parent_interface, RESOLVE_TYPE_NO_CHECK_DISTINCT)) return false;
			Decl *parent_decl = parent_interface->type->decl;
			Decl *value = sema_resolve_method(parent_decl, kw);
			if (value && member)
			{
				RETURN_SEMA_ERROR(expr, "Ambiguous method '%s' on '%s', it was implemented on both '%s' and '%s'.", kw,
					type_to_error_string(parent->type),
					inf->name, parent_decl->name);
			}
			member = value;
			inf = parent_decl;
		}
	}

	if (member && member->decl_kind == DECL_FUNC)
	{
		unit_register_external_symbol(context, member);
	}

	// 11. If we didn't find a match...
	if (!member)
	{
		// 11a. We have a potential embedded struct check:
		Expr *substruct = sema_enter_inline_member(current_parent, type);
		if (substruct)
		{
			current_parent = substruct;
			type = current_parent->type->canonical;
			flat_type = type_flatten(type);
			goto CHECK_DEEPER;
		}

		// 11b. Otherwise we give up.
		if (private)
		{
			if (missing_ref) goto MISSING_REF;
			RETURN_SEMA_ERROR(expr, "The method '%s' has private visibility.", kw);
		}
		if (parent->type->canonical->type_kind == TYPE_INTERFACE)
		{
			if (missing_ref) goto MISSING_REF;
			RETURN_SEMA_ERROR(expr, "The '%s' interface has no method '%s', did you spell it correctly?", parent->type->canonical->name, kw);
		}
		if (missing_ref) goto MISSING_REF;
		RETURN_SEMA_ERROR(expr, "There is no field or method '%s.%s'.", type_to_error_string(parent->type), kw);
	}

	if (!member->unit && !sema_analyse_decl(context, decl)) return false;
	if (!sema_analyse_decl(context, member)) return false;

	ASSERT_SPAN(expr, member->type);
	if (member->decl_kind == DECL_VAR)
	{
		if (member->var.kind == VARDECL_BITMEMBER)
		{
			// Transform bitstruct access to expr_bitaccess.
			expr->expr_kind = EXPR_BITACCESS;
		}
		else if (member->var.kind == VARDECL_MEMBER && expr_is_const_initializer(current_parent))
		{
			if (!sema_expr_fold_to_member(expr, current_parent, member))
			{
				if (member->type->type_kind == TYPE_FLEXIBLE_ARRAY)
				{
					RETURN_SEMA_ERROR(expr, "Could not fold to member '%s', it's a flexible array member which is always empty.", member->name);
				}
				RETURN_SEMA_ERROR(expr, "Could not fold to member '%s' – it wasn't the last assigned member.", member->name);
			}
			return true;
		}
	}
	// 13. Copy properties.

	expr->access_resolved_expr = (ExprResolvedAccess) { .parent = current_parent, .ref = member };
	if (expr->expr_kind == EXPR_ACCESS_UNRESOLVED) expr->expr_kind = EXPR_ACCESS_RESOLVED;
	expr->type = type_add_optional(member->type, optional);
	return true;
MISSING_REF:
	*missing_ref = true;
	return false;
}

static inline Expr **sema_prepare_splat_insert(Expr **exprs, unsigned added, unsigned insert_point)
{
	if (added == 0)
	{
		vec_erase_at(exprs, insert_point);
		return exprs;
	}
	unsigned size = vec_size(exprs);
	ASSERT(size);
	for (unsigned i = 1; i < added; i++)
	{
		vec_add(exprs, NULL);
	}
	// Move everything upwards.
	for (unsigned i = size - 1; i > insert_point; i--)
	{
		exprs[i + added - 1] = exprs[i];
	}
	return exprs;
}
static Expr **sema_vasplat_insert(SemaContext *context, Expr **init_expressions, Expr *expr, unsigned insert_point)
{
	Expr **args = context->macro_varargs;
	unsigned param_count = vec_size(args);
	Range *range = &expr->vasplat_expr;
	Expr *start = exprptrzero(range->start);
	unsigned start_idx = 0;
	if (start)
	{
		if (!sema_analyse_expr_rvalue(context, start)) return NULL;
		if (!expr_is_const_int(start))
		{
			SEMA_ERROR(expr, "Expected a constant integer.");
			return NULL;
		}
		Int start_index = start->const_expr.ixx;
		if (int_is_neg(start_index))
		{
			SEMA_ERROR(expr, "Expected a positive integer.");
			return NULL;
		}
		if (int_bits_needed(start_index) > 31)
		{
			SEMA_ERROR(expr, "Start index is too big.");
			return NULL;
		}
		start_idx = start_index.i.low;
		if (range->start_from_end)
		{
			start_idx = param_count - start_idx;
		}
		if (param_count < start_idx)
		{
			SEMA_ERROR(expr, "Start index exceeds the number of parameters (%d).", start_idx);
			return NULL;
		}
	}
	Expr *end = exprptrzero(range->end);
	unsigned end_idx = param_count;
	if (end)
	{
		if (!sema_analyse_expr_rvalue(context, end)) return NULL;
		if (!expr_is_const_int(end))
		{
			SEMA_ERROR(expr, "Expected a constant integer.");
			return NULL;
		}
		Int end_index = end->const_expr.ixx;
		if (int_is_neg(end_index))
		{
			SEMA_ERROR(expr, "Expected a positive integer.");
			return NULL;
		}
		if (int_bits_needed(end_index) > 31)
		{
			if (range->is_len)
			{
				SEMA_ERROR(expr, "End index is too large.");
			}
			else
			{
				SEMA_ERROR(expr, "Length is too large.");
			}
			return NULL;
		}
		end_idx = end_index.i.low;
		if (range->end_from_end)
		{
			if (end_idx > param_count)
			{
				end_idx = 0;
			}
			else
			{
				end_idx = param_count - end_idx;
			}
		}
		if (range->is_len)
		{
			end_idx = start_idx + end_idx;
		}
		else
		{
			end_idx++;
		}
		if (param_count <= end_idx)
		{
			SEMA_ERROR(expr, "End index would exceed the number of parameters.");
			return NULL;
		}
	}

	unsigned added = end_idx - start_idx;

	// Zero splat
	if (!added)
	{
		vec_erase_at(init_expressions, insert_point);
		return init_expressions;
	}

	init_expressions = sema_prepare_splat_insert(init_expressions, added, insert_point);
	for (unsigned i = start_idx; i < end_idx; i++)
	{
		init_expressions[insert_point + i - start_idx] = copy_expr_single(args[i]);
	}
	return init_expressions;
}

Expr **sema_expand_vasplat_exprs(SemaContext *context, Expr **exprs)
{
	if (!context) return exprs;
	bool in_macro = context->current_macro;
	unsigned count = vec_size(exprs);
	bool expand;
	do
	{
		expand = false;
		for (ArrayIndex i = 0; i < (ArrayIndex)count; i++)
		{
			Expr *arg = exprs[i];
			ExprKind kind = arg->expr_kind;
			if (in_macro && kind == EXPR_VASPLAT)
			{
				exprs = sema_vasplat_insert(context, exprs, arg, i);
				// If we have null back it failed.
				if (!exprs) return NULL;
				count = vec_size(exprs);
				expand = true;
				break;
			}
			if (kind == EXPR_SPLAT)
			{
				Expr *inner = arg->inner_expr;
				if (!sema_analyse_expr_rvalue(context, inner)) return false;
				Type *flat = type_flatten(inner->type);
				switch (flat->type_kind)
				{
					case TYPE_VECTOR:
					case TYPE_ARRAY:
					case TYPE_SLICE:
					case TYPE_UNTYPED_LIST:
						// These may be splatted
						break;
					default:
						SEMA_ERROR(arg, "An argument of type %s cannot be splatted.",
						           type_quoted_error_string(inner->type));
						return NULL;
				}
				ArrayIndex len = sema_len_from_expr(inner);
				if (len == -1)
				{
					SEMA_ERROR(arg,
					           "Splat may not be used with if the length is not known, but if you slice it to a constant length it will work (e.g '...val[:2]')");
					return NULL;
				}
				if (len == 0 && !expr_is_const(arg))
				{
					SEMA_ERROR(arg, "A non-constant zero size splat is not allowed.");
					return NULL;
				}
				Expr **new_args = sema_splat_arraylike_insert(context, exprs, inner, len, i);
				if (!new_args) return false;
				exprs = new_args;
				count = vec_size(exprs);
				expand = true;
				break;
			}
		}
	} while (expand);
	return exprs;
}

static inline bool sema_expr_analyse_expr_list(SemaContext *context, Expr *expr)
{
	bool success = true;
	ByteSize last = vec_size(expr->expression_list) - 1;
	for (unsigned i = 0; i <= last; i++)
	{
		Expr *checked_expr = expr->expression_list[i];
		if (!sema_analyse_expr_rvalue(context, checked_expr)) return false;
		if (i != last && !sema_expr_check_discard(context, checked_expr)) return false;
	}
	expr->type = expr->expression_list[last]->type;
	return success;
}


static inline bool sema_expr_analyse_cast(SemaContext *context, Expr *expr, bool *invalid_cast_ref)
{
	if (invalid_cast_ref) *invalid_cast_ref = false;
	Expr *inner = exprptr(expr->cast_expr.expr);
	TypeInfo *type_info = type_infoptr(expr->cast_expr.type_info);
	if (inner->expr_kind == EXPR_INITIALIZER_LIST || inner->expr_kind == EXPR_DESIGNATED_INITIALIZER_LIST)
	{
		expr->expr_kind = EXPR_COMPOUND_LITERAL;
		expr->expr_compound_literal = (ExprCompoundLiteral) { .initializer = inner, .type_info = type_info };
		return sema_expr_analyse_compound_literal(context, expr, invalid_cast_ref);
	}
	bool success = sema_resolve_type_info(context, type_info, RESOLVE_TYPE_ALLOW_INFER);
	if (!sema_analyse_expr_rvalue(context, inner) || !success) return false;

	Type *target_type = type_info->type;
	if (type_is_optional(target_type))
	{
		RETURN_SEMA_ERROR(type_info, "Casting to an optional type is not allowed.");
	}

	if (invalid_cast_ref)
	{
		if (!cast_explicit_silent(context, inner, target_type))
		{
			*invalid_cast_ref = true;
			return false;
		}
	}
	else
	{
		if (!cast_explicit(context, inner, target_type)) return expr_poison(expr);
	}
	expr_replace(expr, inner);
	return true;
}

static bool sema_expr_analyse_slice_assign(SemaContext *context, Expr *expr, Type *left_type, Expr *right, bool *failed_ref)
{
	Expr *left = exprptr(expr->binary_expr.left);
	Type *left_flat = type_flatten(left_type);
	Type *base = left_flat->array.base;
	if (right->expr_kind == EXPR_SLICE || (compiler.build.old_slice_copy && right->expr_kind == EXPR_INITIALIZER_LIST && right->initializer_list))
	{
		if (!sema_analyse_inferred_expr(context, left_type, right, NULL)) return false;
		if (left_flat == type_flatten(right->type) || right->type == type_untypedlist) goto SLICE_COPY;
	}
	else
	{
		if (right->expr_kind == EXPR_INITIALIZER_LIST && right->initializer_list)
		{
			Type *flat = type_flatten(base);
			switch (flat->type_kind)
			{
				case TYPE_STRUCT:
				case TYPE_ARRAY:
				case TYPE_VECTOR:
				case TYPE_UNION:
				case TYPE_INFERRED_ARRAY:
				case TYPE_INFERRED_VECTOR:
				case TYPE_BITSTRUCT:
				case TYPE_SLICE:
					break;
				default:
					RETURN_SEMA_ERROR(right, "You trying to assign this expression to each element in the slice, but the expression can't be cast to a value of type %s. Maybe you wanted to do a slice copy and forgot to add [..] at the end? Rather than 'a[..] = { ... }', try 'a[..] = { ... }[..]'.",
						type_quoted_error_string(base));
			}
		}
		if (!sema_analyse_inferred_expr(context, base, right, NULL)) return false;
	}
	Type *right_type = right->type->canonical;
	if (base->canonical != right_type && (type_is_arraylike(right_type) || right_type->type_kind == TYPE_SLICE))
	{
		if (right_type->array.base->canonical == base->canonical)
		{
			CHECK_ON_DEFINED(failed_ref);
			RETURN_SEMA_ERROR(right, "You cannot assign a slice, vector or array to a slicing without making an explicit [..] operation, e.g. 'foo[..] = my_array[..]', so you can try adding an explicit slicing to this expression.");
		}
	}
	if (!cast_implicit_checked(context, right, base, false, failed_ref)) return false;
	if (IS_OPTIONAL(right))
	{
		RETURN_SEMA_ERROR(right, "The right hand side may not be optional when using slice assign.");
	}
	expr->expr_kind = EXPR_SLICE_ASSIGN;
	expr->type = right->type;
	expr->slice_assign_expr.left = exprid(left);
	expr->slice_assign_expr.right = exprid(right);
	return true;

SLICE_COPY:;
	while (right->expr_kind == EXPR_SLICE)
	{
		Expr *inner = exprptr(right->slice_expr.expr);
		if (inner->expr_kind != EXPR_IDENTIFIER) break;
		Decl *decl = inner->ident_expr;
		if (decl->decl_kind != DECL_VAR) break;
		if (!decl->var.out_param || decl->var.in_param) break;
		if (context->active_scope.flags & (SCOPE_ENSURE | SCOPE_ENSURE_MACRO)) break;
		RETURN_SEMA_ERROR(right, "'out' parameters may not be read.");
	}

	if (!sema_analyse_expr_rhs(context, left_type, right, false, NULL, false)) return false;
	Range *left_range = &left->slice_expr.range;
	IndexDiff left_len = range_const_len(left_range);
	IndexDiff right_len = 0;
	if (!expr_is_const(right))
	{
		ASSERT_SPAN(right, right->expr_kind == EXPR_SLICE);
		Range *right_range = &right->slice_expr.range;
		right_len = range_const_len(right_range);
	}
	else
	{
		right_len = sema_len_from_const(right);
	}
	if (left_len >= 0 && right_len >= 0 && left_len != right_len)
	{
		CHECK_ON_DEFINED(failed_ref);
		RETURN_SEMA_ERROR(expr, "Length mismatch between slices.");
	}
	expr->expr_kind = EXPR_SLICE_COPY;
	expr->type = left->type;
	expr->slice_assign_expr.left = exprid(left);
	expr->slice_assign_expr.right = exprid(right);
	return true;
}

bool sema_expr_analyse_assign_right_side(SemaContext *context, Expr *expr, Type *left_type, Expr *right,
                                         bool is_unwrapped_var, bool is_declaration, bool *failed_ref)
{
	if (expr && exprptr(expr->binary_expr.left)->expr_kind == EXPR_SLICE)
	{
		return sema_expr_analyse_slice_assign(context, expr, left_type, right, failed_ref);
	}

	// 1. Evaluate right side to required type.
	bool to_optional = left_type && type_is_optional(left_type);
	if (!sema_analyse_expr_rhs(context, left_type, right, is_unwrapped_var || to_optional, failed_ref, is_declaration)) return false;
	if (IS_OPTIONAL(right) && !to_optional)
	{
		if (is_unwrapped_var)
		{
			RETURN_SEMA_ERROR(exprptr(expr->binary_expr.left), "The variable is unwrapped in this context, if you don't want to unwrap it, use () around the variable to suppress unwrapping, like 'catch err = (x)' and 'try (x)'.");
		}
		if (!left_type) left_type = type_no_optional(right->type);
		CHECK_ON_DEFINED(failed_ref);
		return sema_error_failed_cast(context, right, right->type, left_type);
	}

	// 3. Set the result to the type on the right side.
	if (expr) expr->type = right->type;

	return true;
}

static bool sema_expr_analyse_ct_identifier_assign(SemaContext *context, Expr *expr, Expr *left, Expr *right, bool *failed_ref)
{
	ASSERT_SPAN(left, left->resolve_status == RESOLVE_DONE);

	// Evaluate right side to using inference from last type.
	if (!sema_analyse_inferred_expr(context, left->type, right, NULL)) return false;

	if (!expr_is_runtime_const(right))
	{
		if (failed_ref) return *failed_ref = true, false;
		RETURN_SEMA_ERROR(right, "You can only assign constants to a compile time variable.");
	}
	Decl *ident = left->ct_ident_expr.decl;

	ident->var.init_expr = right;
	expr_replace(expr, right);
	ident->type = right->type;
	return true;
}

static bool sema_expr_analyse_ct_subscript_rhs(SemaContext *context, Decl *ct_var, Expr *right)
{
	if (ct_var->type == type_untypedlist)
	{
		if (!sema_analyse_expr_rvalue(context, right)) return false;
	}
	else
	{
		if (!sema_analyse_expr_rhs(context, type_get_indexed_type(ct_var->type), right, false, NULL, false)) return false;
	}
	if (!expr_is_runtime_const(right))
	{
		RETURN_SEMA_ERROR(right, "The argument must be a constant value.");
	}
	return true;
}

static void sema_expr_analyse_ct_subscript_set_value(Expr *left, Decl *ct_var, Expr *right)
{
	ArrayIndex index = left->ct_subscript_expr.index;
	Expr *original_value = ct_var->var.init_expr;
	ASSERT_SPAN(original_value, original_value->expr_kind == EXPR_CONST);
	ExprConst *expr_const = &original_value->const_expr;

	switch (expr_const->const_kind)
	{
		case CONST_FLOAT:
		case CONST_INTEGER:
		case CONST_BOOL:
		case CONST_ENUM:
		case CONST_FAULT:
		case CONST_POINTER:
		case CONST_TYPEID:
		case CONST_REF:
		case CONST_MEMBER:
			UNREACHABLE_VOID
		case CONST_BYTES:
		case CONST_STRING:
		{
			unsigned char *copy = MALLOC(expr_const->bytes.len + 1);
			memcpy(copy, expr_const->bytes.ptr, expr_const->bytes.len + 1);
			assert(right->const_expr.const_kind == CONST_INTEGER);
			copy[index] = (unsigned char)right->const_expr.ixx.i.low;
			expr_const->bytes.ptr = (char *)copy;
			break;
		}
		case CONST_SLICE:
			const_init_rewrite_array_at(expr_const->slice_init, right, index);
			break;
		case CONST_INITIALIZER:
			const_init_rewrite_array_at(expr_const->initializer, right, index);
			break;
		case CONST_UNTYPED_LIST:
			expr_const->untyped_list[index] = right;
			break;
	}
}

static bool sema_expr_analyse_ct_subscript_assign(SemaContext *context, Expr *expr, Expr *left, Expr *right)
{
	Decl *ct_var = left->ct_subscript_expr.var;
	if (!sema_expr_analyse_ct_subscript_rhs(context, ct_var, right)) return false;
	sema_expr_analyse_ct_subscript_set_value(left, ct_var, right);
	expr_replace(expr, right);
	return true;
}


static bool sema_expr_fold_hash(SemaContext *context, Expr *expr)
{
	assert(expr->expr_kind == EXPR_HASH_IDENT);
	while (expr->expr_kind == EXPR_HASH_IDENT)
	{
		ASSERT(expr && expr->hash_ident_expr.identifier);
		DEBUG_LOG("Resolving identifier '%s'", expr->hash_ident_expr.identifier);
		Decl *decl = sema_resolve_symbol(context, expr->hash_ident_expr.identifier, NULL, expr->span);

		// Already handled
		if (!decl) return expr_poison(expr);

		ASSERT_SPAN(expr, decl->decl_kind == DECL_VAR);
		DEBUG_LOG("Replacing expr (%p) with '%s' (%p) expression resolve: %d", expr, expr_kind_to_string(decl->var.init_expr->expr_kind), decl->var.init_expr, decl->var.init_expr->resolve_status);
		bool is_ref = expr->hash_ident_expr.is_ref;
		expr_replace(expr, copy_expr_single(decl->var.init_expr));
		if (is_ref) expr_set_to_ref(expr);
		REMINDER("Handle inlining at");
	}
	return expr_ok(expr);
}
/**
 * Analyse a = b
 * @return true if analysis works
 */
static bool sema_expr_analyse_assign(SemaContext *context, Expr *expr, Expr *left, Expr *right, bool *failed_ref)
{
	if (!sema_analyse_expr_lvalue(context, left, failed_ref)) return false;
	switch (left->expr_kind)
	{
		case EXPR_CT_IDENT:
			// $foo = ...
			return sema_expr_analyse_ct_identifier_assign(context, expr, left, right, failed_ref);
		case EXPR_CT_SUBSCRIPT:
			return sema_expr_analyse_ct_subscript_assign(context, expr, left, right);
		default:
			break;
	}
	if (left->type && left->type->type_kind == TYPE_FLEXIBLE_ARRAY)
	{
		RETURN_SEMA_ERROR(left, "You can't assign to a flexible array member, but you may index into it and mutate it that way.");
	}

	// 2. Check assignability
	if (!sema_expr_check_assign(context, left, failed_ref)) return false;

	bool is_unwrapped_var = expr_is_unwrapped_ident(left);

	Module *generic = type_find_generic(left->type);
	if (generic)
	{
		Module *temp = context->generic.infer;
		context->generic.infer = generic;
		generic = temp;
	}
	// 3. Evaluate right side to required type.
	if (!sema_expr_analyse_assign_right_side(context, expr, left->type, right, is_unwrapped_var, false, failed_ref))
	{
		context->generic.infer = generic;
		return false;
	}
	context->generic.infer = generic;
	if (is_unwrapped_var && IS_OPTIONAL(right))
	{
		sema_rewrap_var(context, left->ident_expr);
		return true;
	}
	if (left->expr_kind == EXPR_SUBSCRIPT_ASSIGN)
	{
		Decl *temp = decl_new_generated_var(right->type, VARDECL_LOCAL, right->span);
		Expr *expr_rh = expr_generate_decl(temp, right);
		Expr **args = NULL;
		vec_add(args, exprptr(left->subscript_assign_expr.index));
		vec_add(args, expr_variable(temp));
		if (!sema_insert_method_call(context, expr, declptr(left->subscript_assign_expr.method), exprptr(left->subscript_assign_expr.expr), args, false)) return false;
		expr_rewrite_two(expr, expr_rh, expr_copy(expr));
		return true;
	}
	if (left->expr_kind == EXPR_BITACCESS)
	{
		if (!sema_bit_assignment_check(context, right, left->access_resolved_expr.ref, failed_ref)) return false;
		expr->expr_kind = EXPR_BITASSIGN;
	}
	return true;
}


/**
 * Analyse define $foo = ...
 *
 * @return true if analysis worked.
 */
static bool sema_binary_analyse_ct_op_assign(SemaContext *context, Expr *expr, Expr *left)
{
	ASSERT_SPAN(left, left->expr_kind == EXPR_CT_IDENT);

	Decl *left_var = left->ct_ident_expr.decl;
	if (!sema_cast_ct_ident_rvalue(context, left)) return false;

	expr->binary_expr.operator = binaryop_assign_base_op(expr->binary_expr.operator);

	if (!sema_expr_analyse_binary(context, NULL, expr, NULL)) return false;
	expr->resolve_status = RESOLVE_DONE;

	if (!expr_is_runtime_const(expr))
	{
		RETURN_SEMA_ERROR(expr, "Expected this to result in a constant expression.");
	}

	left_var->var.init_expr = expr;
	left_var->type = expr->type;
	return true;
}

/**
 * Analyse $foo[1] += ...
 *
 * @return true if analysis worked.
 */
static bool sema_binary_analyse_ct_subscript_op_assign(SemaContext *context, Expr *expr, Expr *left)
{
	ASSERT_SPAN(left, left->expr_kind == EXPR_CT_SUBSCRIPT);

	Decl *left_var = left->ct_subscript_expr.var;
	ArrayIndex idx = left->ct_subscript_expr.index;
	Expr *lhs_expr = left_var->var.init_expr;
	ASSERT_SPAN(lhs_expr, lhs_expr->expr_kind == EXPR_CONST);
	Expr *value = expr_from_const_expr_at_index(lhs_expr, idx);

	BinaryOp op = binaryop_assign_base_op(expr->binary_expr.operator);
	expr->binary_expr = (ExprBinary) { .left = exprid(value), .right = expr->binary_expr.right, .operator = op };
	if (!sema_expr_analyse_binary(context, NULL, expr, NULL)) return false;
	sema_expr_analyse_ct_subscript_set_value(left, left_var, expr);
	return true;

}

static BoolErr sema_insert_overload_in_op_assign_or_error(SemaContext *context, Expr *expr, Expr *left, Expr *right, BinaryOp operator, Type *lhs_type)
{
	assert(type_is_user_defined(lhs_type));
	if (!sema_analyse_inferred_expr(context, lhs_type, right, NULL)) return BOOL_ERR;
	static OperatorOverload MAP[BINARYOP_LAST + 1] = {
		[BINARYOP_ADD_ASSIGN] = OVERLOAD_PLUS_ASSIGN,
		[BINARYOP_SUB_ASSIGN] = OVERLOAD_MINUS_ASSIGN,
		[BINARYOP_MULT_ASSIGN] = OVERLOAD_MULTIPLY_ASSIGN,
		[BINARYOP_DIV_ASSIGN] = OVERLOAD_DIVIDE_ASSIGN,
		[BINARYOP_MOD_ASSIGN] = OVERLOAD_REMINDER_ASSIGN,
		[BINARYOP_BIT_XOR_ASSIGN] = OVERLOAD_XOR_ASSIGN,
		[BINARYOP_BIT_OR_ASSIGN] = OVERLOAD_OR_ASSIGN,
		[BINARYOP_BIT_AND_ASSIGN] = OVERLOAD_AND_ASSIGN,
		[BINARYOP_SHL_ASSIGN] = OVERLOAD_SHL_ASSIGN,
		[BINARYOP_SHR_ASSIGN] = OVERLOAD_SHR_ASSIGN,
	};
	OperatorOverload overload = MAP[operator];
	assert(overload && "Overload not mapped");
	if (!sema_replace_with_overload(context, expr, left, right, lhs_type, &overload)) return BOOL_ERR;
	if (!overload)
	{
		return BOOL_TRUE;
	}
	return BOOL_FALSE;
}

INLINE bool sema_rewrite_op_assign(SemaContext *context, Expr *expr, Expr *left, Expr *right, BinaryOp new_op)
{
	// Simple case: f += a => f = f + a
	if (left->expr_kind == EXPR_IDENTIFIER)
	{
		Expr *lvalue = expr_copy(left);
		Expr *rvalue = expr_copy(left);
		expr->expr_kind = EXPR_BINARY;
		left->expr_kind = EXPR_BINARY;
		left->binary_expr = (ExprBinary) { .left = exprid(rvalue), .right = exprid(right), .operator = new_op };
		expr->binary_expr = (ExprBinary) { .left = exprid(lvalue), .right = exprid(left), .operator = BINARYOP_ASSIGN, .grouped = false };
		expr->resolve_status = RESOLVE_NOT_DONE;
		left->resolve_status = RESOLVE_NOT_DONE;
		return sema_analyse_expr_rvalue(context, expr);
	}

	Type *lhs_type = type_no_optional(left->type)->canonical;
	Decl *variable = decl_new_generated_var(type_get_ptr(left->type), VARDECL_LOCAL, left->span);
	Expr *left_copy = expr_copy(left);

	// If we have a &[] overload, then replace left copy with that one.
	if (left->expr_kind == EXPR_SUBSCRIPT_ASSIGN)
	{
		Expr *parent = exprptr(left->subscript_assign_expr.expr);
		Type *parent_type = type_no_optional(parent->type)->canonical;
		Decl *operator = sema_find_untyped_operator(parent_type, OVERLOAD_ELEMENT_REF, NULL);
		Expr *index = exprptr(left->subscript_assign_expr.index);
		if (operator)
		{
			Expr **args = NULL;
			vec_add(args, index);
			if (!sema_insert_method_call(context, left_copy, operator, parent, args, false)) return false;
			goto AFTER_ADDR;
		}
		// If we only have []=, then we need []
		operator = sema_find_untyped_operator(parent_type, OVERLOAD_ELEMENT_AT, NULL);
		if (!operator)
		{
			RETURN_SEMA_ERROR(left, "There is no overload for [] for %s.", type_quoted_error_string(type_no_optional(left->type)));
		}
		Type *return_type = typeget(operator->func_decl.signature.rtype);
		if (type_no_optional(return_type->canonical) != lhs_type->canonical)
		{
			RETURN_SEMA_ERROR(expr, "There is a type mismatch between the overload for [] and []= for %s.", type_quoted_error_string(type_no_optional(left->type)));
		}
		// First we want to create the indexed value and the index:
		Decl *index_val = decl_new_generated_var(index->type, VARDECL_LOCAL, index->span);
		// We need to take the address of the parent here, otherwise this might fail,
		expr_insert_addr(parent);
		Decl *parent_val = decl_new_generated_var(parent->type, VARDECL_LOCAL, parent->span);
		Expr **list = NULL;
		// temp = parent, temp_2 = index
		vec_add(list, expr_generate_decl(parent_val, parent));
		vec_add(list, expr_generate_decl(index_val, index));
		// Now, create a lhs of the binary add:
		Expr *lhs = expr_new_expr(EXPR_SUBSCRIPT, left);
		Expr *parent_by_variable = expr_variable(parent_val);
		if (!sema_expr_rewrite_insert_deref(context, parent_by_variable)) return false;
		lhs->subscript_expr = (ExprSubscript) { .expr = exprid(parent_by_variable), .index.expr = exprid(expr_variable(index_val)) };
		// Now create the binary expression
		Expr *binary = expr_new_expr(EXPR_BINARY, expr);
		binary->binary_expr = (ExprBinary) { .left = exprid(lhs), .right = exprid(right), .operator = new_op };
		// Finally, we need the assign, and here we just need to replace
		Expr *assign = expr_new_expr(EXPR_BINARY, expr);
		assign->binary_expr = (ExprBinary) { .left = exprid(left), .right = exprid(binary), .operator = BINARYOP_ASSIGN };
		// Now we need to patch the values in `left`:
		parent_by_variable = expr_variable(parent_val);
		if (!sema_expr_rewrite_insert_deref(context, parent_by_variable)) return false;
		left->subscript_assign_expr.expr = exprid(parent_by_variable);
		left->subscript_assign_expr.index = exprid(expr_variable(index_val));
		// We add the assign
		vec_add(list, assign);
		// And rewrite the expression to an expression list:
		expr->expr_kind = EXPR_EXPRESSION_LIST;
		expr->expression_list = list;
		return sema_expr_analyse_expr_list(context, expr);
	}

	// f => &f
	expr_insert_addr(left_copy);

AFTER_ADDR:;

	// temp = &f
	Expr *init = expr_generate_decl(variable, left_copy);
	// lvalue = temp, rvalue = temp
	Expr *left_rvalue = expr_variable(variable);
	Expr *left_lvalue = expr_variable(variable);

	// lvalue = *temp, rvalue = *temp
	if (!sema_expr_rewrite_insert_deref(context, left_lvalue)) return false;
	if (!sema_expr_rewrite_insert_deref(context, left_rvalue)) return false;

	// init, expr -> lvalue = rvalue + a
	expr->expr_kind = EXPR_BINARY;
	left->expr_kind = EXPR_BINARY;
	left->binary_expr = (ExprBinary) { .left = exprid(left_lvalue), .right = exprid(right), new_op };
	expr->binary_expr = (ExprBinary) { .left = exprid(left_rvalue), .right = exprid(left), .operator = BINARYOP_ASSIGN, .grouped = false };
	expr->resolve_status = RESOLVE_NOT_DONE;
	left->resolve_status = RESOLVE_NOT_DONE;
	Expr *binary = expr_copy(expr);
	expr_rewrite_two(expr, init, binary);
	return sema_analyse_expr_rvalue(context, expr);
}

static bool sema_expr_analyse_op_assign_enum_ptr(SemaContext *context, Expr *rhs, Type *flat, Type *base, Type *flat_rhs, bool is_enum, BinaryOp op)
{
	if (flat == base)
	{
		if (!type_is_integer(flat_rhs))
		{
			RETURN_SEMA_ERROR(rhs,
							  "The right side was '%s' but only integers are valid on the right side of %s when the left side is %s.",
							  type_to_error_string(rhs->type),
							  token_type_to_string(binaryop_to_token(op)), is_enum ? "an enum" : "a pointer");
		}
		Type *to = is_enum ? enum_inner_type(flat) : type_isz;
		if (!cast_implicit(context, rhs, to, true)) return false;
	}
	else
	{
		Type *real_type = type_get_vector(is_enum ? enum_inner_type(base) : type_isz, flat->array.len);
		if (flat_rhs == type_untypedlist)
		{
			if (!cast_implicit(context, rhs, real_type, true)) return false;
			flat_rhs = type_flat_for_arithmethics(rhs->type);
		}
		if (!type_is_integer(flat_rhs) && (flat_rhs->type_kind != TYPE_VECTOR || !type_is_integer(flat_rhs->array.base)))
		{
			RETURN_SEMA_ERROR(rhs,
							  "The right side was '%s' but only integers or integer vectors are valid on the right side of %s when the left side is %s.",
							  type_to_error_string(rhs->type),
							  token_type_to_string(binaryop_to_token(op)), is_enum ? "an enum vector" : "a pointer vector");
		}
		if (!cast_implicit(context, rhs, real_type, true)) return false;
	}
	return true;
}

/**
 * Analyse *= /= %= ^= |= &= += -= <<= >>=
 *
 * @return true if analysis worked.
 */
static bool sema_expr_analyse_op_assign(SemaContext *context, Expr *expr, Expr *left, Expr *right, BinaryOp operator)
{
	bool *failed_ref = NULL;
	bool is_bit_op = false;
	bool is_shift = false;
	bool is_add_sub = false;
	bool int_only = false;
	switch (operator)
	{
		case BINARYOP_MULT_ASSIGN:
		case BINARYOP_DIV_ASSIGN:
			break;
		case BINARYOP_BIT_AND_ASSIGN:
		case BINARYOP_BIT_OR_ASSIGN:
		case BINARYOP_BIT_XOR_ASSIGN:
			int_only = true;
			is_bit_op = true;
			break;
		case BINARYOP_ADD_ASSIGN:
		case BINARYOP_SUB_ASSIGN:
			is_add_sub = true;
			break;
		case BINARYOP_MOD_ASSIGN:
			int_only = true;
			break;
		case BINARYOP_SHL_ASSIGN:
		case BINARYOP_SHR_ASSIGN:
			is_shift = true;
			int_only = true;
			break;
		default:
			UNREACHABLE

	}
	// 1. Analyse left side.
	if (!sema_analyse_expr_lvalue(context, left, NULL)) return false;

	switch (left->expr_kind)
	{
		case EXPR_CT_IDENT:
			return sema_binary_analyse_ct_op_assign(context, expr, left);
		case EXPR_CT_SUBSCRIPT:
			return sema_binary_analyse_ct_subscript_op_assign(context, expr, left);
		default:
			break;
	}

	if (left->type->type_kind == TYPE_FLEXIBLE_ARRAY)
	{
		RETURN_SEMA_ERROR(left, "You can't assign to a flexible array member, but you may index into it and mutate it that way.");
	}

	// 2. Verify that the left side is assignable.

	if (!sema_expr_check_assign(context, left, NULL)) return false;

	Type *left_type_canonical = left->type->canonical;

	// 3. Check that it is readable
	if (!sema_cast_rvalue(context, left, false)) return false;

	Type *no_fail = type_no_optional(left->type);

	Type *canonical = no_fail->canonical;
	if (type_is_user_defined(canonical))
	{
		if (canonical->type_kind == TYPE_BITSTRUCT && is_bit_op) goto SKIP_OVERLOAD_CHECK;
		BoolErr b = sema_insert_overload_in_op_assign_or_error(context, expr, left, right, operator, no_fail->canonical);
		if (b == BOOL_ERR) return false;
		if (b == BOOL_TRUE) return true;
		// Maybe we have the corresponding implemented % * etc
		// The right hand side is now already checked.
		BinaryOp underlying_op = binaryop_assign_base_op(operator);
		static OperatorOverload MAP[BINARYOP_LAST + 1] = {
			[BINARYOP_ADD] = OVERLOAD_PLUS,
			[BINARYOP_SUB] = OVERLOAD_MINUS,
			[BINARYOP_DIV] = OVERLOAD_DIVIDE,
			[BINARYOP_MOD] = OVERLOAD_REMINDER,
			[BINARYOP_MULT] = OVERLOAD_MULTIPLY,
			[BINARYOP_BIT_XOR] = OVERLOAD_XOR,
			[BINARYOP_BIT_OR] = OVERLOAD_OR,
			[BINARYOP_BIT_AND] = OVERLOAD_AND,
			[BINARYOP_SHL] = OVERLOAD_SHL,
			[BINARYOP_SHR] = OVERLOAD_SHR,
		};
		OperatorOverload mapped_overload = MAP[underlying_op];
		bool reverse = false;
		Decl *candidate = expr_may_ref(left) ? sema_find_typed_operator(context, mapped_overload, expr->span, left, right, &reverse) : NULL;
		if (!decl_ok(candidate)) return false;
		if (candidate && typeget(candidate->func_decl.signature.rtype)->canonical == canonical)
		{
			return sema_rewrite_op_assign(context, expr, left, right, underlying_op);
		}
	}
SKIP_OVERLOAD_CHECK:;
	// 3. If this is only defined for ints (^= |= &= %=) verify that this is an int.
	Type *flat = type_flat_for_arithmethics(no_fail);
	Type *base = flat->type_kind == TYPE_VECTOR ? type_flat_for_arithmethics(flat->array.base) : flat;

	if (int_only && !type_is_integer(base))
	{
		if (is_bit_op && (flat->type_kind == TYPE_BITSTRUCT || flat == type_bool || type_flat_is_bool_vector(flat))) goto BITSTRUCT_OK;
		RETURN_SEMA_ERROR(left, "Expected an integer here, not a value of type %s.", type_quoted_error_string(left->type));
	}

	// 4. In any case, these ops are only defined on numbers.
	if (!type_is_numeric(base) && !(is_add_sub && type_underlying_may_add_sub(base)))
	{
		RETURN_SEMA_ERROR(left, "Expected a numeric type here, not a value of type %s.", type_quoted_error_string(left->type));
	}


BITSTRUCT_OK:

	// 5. Analyse RHS
	if (base->type_kind == TYPE_ENUM || base->type_kind == TYPE_POINTER )
	{
		if (!sema_analyse_expr_rvalue(context, right)) return false;
	}
	else
	{
		if (!sema_analyse_inferred_expr(context, left->type, right, NULL)) return false;
	}

	// 3. Copy type & set properties.
	if (IS_OPTIONAL(right) && !IS_OPTIONAL(left))
	{
		RETURN_SEMA_ERROR(right, "Cannot assign an optional value to a non-optional.");
	}

	expr->type = left->type;
	bool optional = IS_OPTIONAL(left) || IS_OPTIONAL(right);

	Type *type_rhs_inline = type_flat_for_arithmethics(right->type);


	// 5. In the enum case we have to treat this differently.
	if (base->type_kind == TYPE_ENUM)
	{
		if (!sema_expr_analyse_op_assign_enum_ptr(context, right, flat, base, type_rhs_inline, true, expr->binary_expr.operator)) return false;
		goto END;
	}
	if (base->type_kind == TYPE_POINTER)
	{
		if (!sema_expr_analyse_op_assign_enum_ptr(context, right, flat, base, type_rhs_inline, false, expr->binary_expr.operator)) return false;
		goto END;
	}

	if (is_shift)
	{
		if (!sema_expr_check_shift_rhs(context, expr, left, type_flatten_and_inline(left->type), right, type_flatten_and_inline(right->type), failed_ref, true))
		{
			return false;
		}
	}
	else
	{
		// Otherwise cast left to right.
		if (!cast_implicit_binary(context, right, no_fail, failed_ref)) return false;
	}

	// 6. Check for zero in case of div or mod.
	if (sema_cast_const(right))
	{
		switch (operator)
		{
			case BINARYOP_DIV_ASSIGN:
			case BINARYOP_MOD_ASSIGN:
				switch (right->const_expr.const_kind)
				{
					case CONST_INTEGER:
						if (int_is_zero(right->const_expr.ixx)) RETURN_SEMA_ERROR(right, operator == BINARYOP_MOD_ASSIGN ? "% by zero not allowed." : "Division by zero not allowed.");
						break;
					case CONST_FLOAT:
						if (right->const_expr.fxx.f == 0) RETURN_SEMA_ERROR(right, operator == BINARYOP_MOD_ASSIGN ? "% by zero not allowed." : "% by zero not allowed.");
						break;
					default:
						break;
				}
				break;
			case BINARYOP_SHL_ASSIGN:
			case BINARYOP_SHR_ASSIGN:
				if (expr_is_const_int(right))
				{
					//  Make sure the value does not exceed the bitsize of
					//     the left hand side.
					if (int_ucomp(right->const_expr.ixx, left->type->canonical->builtin.bitsize, BINARYOP_GT))
					{
						RETURN_SEMA_ERROR(right, "The shift exceeds bitsize of '%s'.", type_to_error_string(left->type));
					}

					// 4b. Make sure that the right hand side is positive.
					if (int_is_neg(right->const_expr.ixx))
					{
						RETURN_SEMA_ERROR(right, "A shift must be a positive number.");
					}
				}
				break;
			default:
				break;
		}
	}

	if (left->expr_kind == EXPR_BITACCESS)
	{
		expr->expr_kind = EXPR_BITASSIGN;
	}

END:
	// Handle the subscript assign variant.
	if (left->expr_kind == EXPR_SUBSCRIPT_ASSIGN)
	{
		return sema_analyse_assign_mutate_overloaded_subscript(context, expr, left, left_type_canonical);
	}
	// 7. Assign type
	expr->type = type_add_optional(left->type, optional);
	return true;

}

static bool sema_replace_with_overload(SemaContext *context, Expr *expr, Expr *left, Expr *right, Type *left_type, OperatorOverload* operator_overload_ref)
{
	assert(!type_is_optional(left_type) && left_type->canonical == left_type);
	bool reverse;
	Decl *overload = sema_find_typed_operator(context, *operator_overload_ref, expr->span, left, right, &reverse);
	if (!decl_ok(overload)) return false;
	if (overload)
	{
		*operator_overload_ref = (OperatorOverload)0; // NOLINT
		return sema_insert_binary_overload(context, expr, overload, left, right, reverse);
	}
	return true;
}

static inline bool sema_check_untyped_promotion(SemaContext *context, Expr *expr, bool is_left, CanonicalType *max_flat, Type *max)
{
	Type *flat = type_flatten(expr->type);
	if (!type_is_unsigned(flat) || type_size(max_flat) != type_size(flat)) return true;
	if (sema_cast_const(expr) && expr_is_const_int(expr) && expr_const_will_overflow(&expr->const_expr, max_flat->type_kind))
	{
		RETURN_SEMA_ERROR(expr,
			"This expression (%s) will be implicitly converted to a signed type due to the %s-hand side being signed, but the value does not fit %s. "
			"To fix this, either cast the value explicitly, or make the %s-hand side an unsigned type.",
			expr_const_to_error_string(&expr->const_expr), is_left ? "right" : "left", type_quoted_error_string(max),
			is_left ? "right" : "left");
	}

	return true;

}
static bool sema_binary_arithmetic_promotion(SemaContext *context, Expr *left, Expr *right, Type *left_type, Type *right_type,
											 Expr *parent, const char *error_message, bool allow_bool_vec,
											 OperatorOverload *operator_overload_ref,
											 bool *failed_ref)
{
	OperatorOverload overload = *operator_overload_ref;
	if (type_is_user_defined(left_type) || type_is_user_defined(right_type))
	{
		if (!sema_replace_with_overload(context, parent, left, right, left_type, operator_overload_ref)) return false;
		if (!*operator_overload_ref) return true;
	}

	if (overload == OVERLOAD_PLUS)
	{
		Type *cast_to_iptr = defer_iptr_cast(left);
		if (!cast_to_iptr) cast_to_iptr = defer_iptr_cast(right);
		left_type = type_no_optional(left->type)->canonical;
		right_type = type_no_optional(right->type)->canonical;
		if (type_is_pointer_like(left_type))
		{
			*operator_overload_ref = OVERLOAD_NONE;
			return sema_expr_analyse_ptr_add(context, parent, left, right, left_type, right_type, cast_to_iptr, failed_ref);
		}
		if (type_is_pointer_like(right_type))
		{
			*operator_overload_ref = OVERLOAD_NONE;
			return sema_expr_analyse_ptr_add(context, parent, right, left, right_type, left_type, cast_to_iptr, failed_ref);
		}
	}

	Type *max = cast_numeric_arithmetic_promotion(type_find_max_type(left_type, right_type, left, right));
	if (!max || (!type_underlying_is_numeric(max) && !(allow_bool_vec && type_flat_is_bool_vector(max))))
	{
		CHECK_ON_DEFINED(failed_ref);
		if (!error_message)
		{
			return sema_type_error_on_binop(context, parent);
		}
		RETURN_SEMA_ERROR(parent, error_message, type_quoted_error_string(left->type), type_quoted_error_string(right->type));
	}
	Type *flat_max = type_flatten(max);
	if (type_is_signed(flat_max))
	{
		if (!sema_check_untyped_promotion(context, left, true, flat_max, max)) return false;
		if (!sema_check_untyped_promotion(context, right, false, flat_max, max)) return false;
	}
	return cast_implicit_binary(context, left, max, failed_ref) &&
		   cast_implicit_binary(context, right, max, failed_ref);
}

static void sema_binary_unify_voidptr(Expr *left, Expr *right, Type **left_type_ref, Type **right_type_ref)
{
	if (*left_type_ref == *right_type_ref) return;
	if (*left_type_ref == type_voidptr)
	{
		cast_no_check(left, *right_type_ref, IS_OPTIONAL(left));
		*left_type_ref = *right_type_ref;
	}
	if (*right_type_ref == type_voidptr)
	{
		cast_no_check(right, *left_type_ref, IS_OPTIONAL(right));
		*right_type_ref = *left_type_ref;
	}
}

static Type *defer_iptr_cast(Expr *maybe_pointer)
{
	// Do we have (iptr)(ptr) +- rhs? If so we change it to
	// (iptr)((char*)(ptr) +- 1)
	if (maybe_pointer->expr_kind == EXPR_PTR_TO_INT
		&& type_flatten(maybe_pointer->type) == type_flatten(type_iptr))
	{
		maybe_pointer->expr_kind = EXPR_RVALUE;
		Type *cast_to_iptr = maybe_pointer->type;
		maybe_pointer->type = type_get_ptr(type_char);
		return cast_to_iptr;
	}
	return NULL;
}

static bool sema_expr_analyse_enum_add_sub(SemaContext *context, Expr *expr, Expr *left, Expr *right, bool *failed_ref)
{
	Type *left_type = type_no_optional(left->type)->canonical;
	bool is_sub = expr->binary_expr.operator == BINARYOP_SUB;
	if (left_type->type_kind != TYPE_ENUM)
	{
		if (is_sub)
		{
			RETURN_SEMA_ERROR(right, "You can't subtract an enum from a value.");
		}
		Expr *temp = right;
		right = left;
		left = temp;
		left_type = type_no_optional(left->type)->canonical;
	}

	// Enum - value / Enum + value
	sema_expr_convert_enum_to_int(left);
	if (!cast_implicit(context, right, left->type, true)) return false;
	expr->type = type_add_optional(left_type, IS_OPTIONAL(left) || IS_OPTIONAL(right));
	if (expr_both_const_foldable(left, right, BINARYOP_ADD))
	{
		Int i;
		if (is_sub)
		{
			i = int_sub(left->const_expr.ixx, right->const_expr.ixx);
		}
		else
		{
			i = int_add(left->const_expr.ixx, right->const_expr.ixx);
		}
		Decl **enums = left_type->decl->enums.values;
		if (int_is_neg(i) || int_ucomp(i, vec_size(enums), BINARYOP_GE))
		{
			if (failed_ref) return *failed_ref = true, false;
			RETURN_SEMA_ERROR(expr, "This does not result in a valid enum. If you want to do the %s, cast the enum to an integer.", is_sub ? "subtraction" : "addition");
		}
		ASSERT_SPAN(expr, left_type->decl->resolve_status == RESOLVE_DONE);
		expr->const_expr = (ExprConst) { .const_kind = CONST_ENUM, .enum_val = enums[int_to_i64(i)] };
		expr->expr_kind = EXPR_CONST;
		expr->resolve_status = RESOLVE_DONE;
	}
	return true;

}

INLINE bool sema_expr_analyse_ptr_sub(SemaContext *context, Expr *expr, Expr *left, Expr *right, CanonicalType *left_type, Type *cast_to_iptr, bool *failed_ref)
{
	Type *right_type = type_no_optional(right->type)->canonical;
	bool left_is_ptr_vector = left_type->type_kind != TYPE_POINTER;
	ArraySize vec_len = left_is_ptr_vector ? left_type->array.len : 0;
	// We restore the type to ensure distinct types are tested against each other.
	left_type = type_no_optional(left->type)->canonical;

	bool right_is_pointer_vector = type_is_pointer_vector(right_type);
	bool right_is_pointer = right_is_pointer_vector || right_type->type_kind == TYPE_POINTER;

	Type *offset_type = vec_len ? type_get_vector(type_isz, vec_len) : type_isz;

	// 3. ptr - other pointer
	if (right_is_pointer)
	{
		// Restore the type
		right_type = type_no_optional(right->type)->canonical;

		// 3a. Require that both types are the same.
		sema_binary_unify_voidptr(left, right, &left_type, &right_type);
		if (left_type != right_type)
		{
			CHECK_ON_DEFINED(failed_ref);
			RETURN_SEMA_ERROR(expr, "'%s' - '%s' is not allowed. Subtracting pointers of different types is not allowed.",
				type_to_error_string(left_type), type_to_error_string(right_type));
		}

		if (expr_both_const_foldable(left, right, BINARYOP_SUB))
		{
			expr_rewrite_const_int(expr, type_isz, (left->const_expr.ptr - right->const_expr.ptr) /
			                                       type_size(left_type->pointer));
			return true;
		}
		// 3b. Set the type
		expr->type = offset_type;
		return true;
	}

	right_type = right->type->canonical;

	bool right_is_vector = right_type->type_kind == TYPE_VECTOR;
	// 4. Check that the right hand side is an integer.
	if (!type_flat_is_intlike(right_type))
	{
		CHECK_ON_DEFINED(failed_ref);
		RETURN_SEMA_ERROR(expr, "Cannot subtract '%s' from '%s'", type_to_error_string(right_type),
		                  type_to_error_string(left_type));
	}

	// 5. Make sure that the integer does not exceed isz in size.
	ArraySize max_size = right_is_vector ? type_size(offset_type) : type_size(type_isz);
	if (type_size(right_type) > max_size)
	{
		CHECK_ON_DEFINED(failed_ref);
		RETURN_SEMA_ERROR(expr, "Cannot subtract %s from a %s, you need to add an explicit a narrowing cast to %s.",
		                  type_quoted_error_string(right->type),
		                  left_is_ptr_vector ? "pointer vector" : "pointer",
		                  type_quoted_error_string(right_is_vector ? offset_type : type_isz));
	}

	// 6. Convert to isz
	if (!cast_implicit_binary(context, right, offset_type, failed_ref)) return false;

	if (left->expr_kind == EXPR_POINTER_OFFSET)
	{
		SourceSpan old_span = expr->span;
		*expr = *left;
		left->span = old_span;
		left->expr_kind = EXPR_BINARY;
		left->binary_expr = (ExprBinary){
			.operator = BINARYOP_SUB,
			.left = expr->pointer_offset_expr.offset,
			.right = exprid(right)
		};
		left->resolve_status = RESOLVE_NOT_DONE;
		if (!sema_analyse_expr_rvalue(context, left)) return false;
		expr->pointer_offset_expr.offset = exprid(left);
	}
	else
	{
		expr->expr_kind = EXPR_POINTER_OFFSET;
		expr->pointer_offset_expr.ptr = exprid(left);
		expr->pointer_offset_expr.offset = exprid(expr_negate_expr(right));
		expr->pointer_offset_expr.raw_offset = false;
	}

	expr->resolve_status = RESOLVE_NOT_DONE;
	if (!sema_analyse_expr_rvalue(context, expr)) return false;

	if (cast_to_iptr)
	{
		expr->resolve_status = RESOLVE_DONE;
		return cast_explicit(context, expr, cast_to_iptr);
	}

	return true;
}
/**
 * Analyse a - b
 * @return true if analysis succeeded
 */
static bool sema_expr_analyse_sub(SemaContext *context, Expr *expr, Expr *left, Expr *right, bool *failed_ref)
{
	// 1. Analyse a and b.
	if (!sema_binary_analyse_subexpr(context, left, right)) return false;

	// Do we have (iptr)(ptr) - rhs? If so we change it to
	// (iptr)((char*)(ptr) - 1)
	Type *cast_to_iptr = defer_iptr_cast(left);

	Type *left_type = type_no_optional(left->type)->canonical;
	Type *right_type = type_no_optional(right->type)->canonical;

	bool left_is_pointer_vector = type_is_pointer_vector(left_type);
	bool left_is_pointer = left_is_pointer_vector || left_type->type_kind == TYPE_POINTER;

	// 2. Handle the ptr - x and ptr - other_pointer
	if (left_is_pointer)
	{
		return sema_expr_analyse_ptr_sub(context, expr, left, right, left_type, cast_to_iptr, failed_ref);
	}

	// Enum - Enum and Enum - int
	if (left_type->type_kind == TYPE_ENUM)
	{
		return sema_expr_analyse_enum_add_sub(context, expr, left, right, failed_ref);
	}

	// 7. Attempt arithmetic promotion, to promote both to a common type.
	OperatorOverload overload = OVERLOAD_MINUS;
	if (!sema_binary_arithmetic_promotion(context, left, right, left_type, right_type, expr,
										  "The subtraction %s - %s is not possible.", false, &overload, failed_ref))
	{
		return false;
	}

	if (!overload) return true;

	// 8. Handle constant folding.
	if (expr_both_const_foldable(left, right, BINARYOP_SUB))
	{
		expr_replace(expr, left);
		switch (left->const_expr.const_kind)
		{
			case CONST_INTEGER:
				expr->const_expr.ixx = int_sub(left->const_expr.ixx, right->const_expr.ixx);
				break;
			case CONST_FLOAT:
				expr->const_expr.fxx = float_sub(left->const_expr.fxx, right->const_expr.fxx);
				break;
			default:
				UNREACHABLE
		}
	}

	expr_binary_unify_failability(expr, left, right);

	return true;

}

INLINE bool sema_expr_analyse_ptr_add(SemaContext *context, Expr *expr, Expr *left, Expr *right, CanonicalType *left_type, CanonicalType *right_type, Type *cast_to_iptr, bool *failed_ref)
{
	bool left_is_vec = left_type->type_kind == TYPE_VECTOR;
	bool right_is_vec = right_type->type_kind == TYPE_VECTOR;
	ArraySize vec_len = left_is_vec ? left_type->array.len : 0;

	// 3a. Check that the other side is an integer of some sort.
	if (!type_is_integer(right_type))
	{
		if (!left_is_vec || !right_is_vec || !type_is_integer(right_type->array.base))
		{
			CHECK_ON_DEFINED(failed_ref);
			RETURN_SEMA_ERROR(right, "A value of type '%s' cannot be added to '%s', an integer was expected here.",
			                  type_to_error_string(right->type),
			                  type_to_error_string(left->type));
		}
	}

	// 3b. Cast it to usz or isz depending on underlying type.
	//     Either is fine, but it looks a bit nicer if we actually do this and keep the sign.
	bool success = cast_explicit(context, right, left_is_vec ? type_get_vector(type_isz, vec_len) : type_isz);

	// No need to check the cast we just ensured it was an integer.
	ASSERT_SPAN(expr, success && "This should always work");
	(void) success;

	// Folding offset.
	if (left->expr_kind == EXPR_POINTER_OFFSET)
	{
		SourceSpan old_span = expr->span;
		*expr = *left;
		left->span = old_span;
		left->expr_kind = EXPR_BINARY;
		left->binary_expr = (ExprBinary){
			.operator = BINARYOP_ADD,
			.left = expr->pointer_offset_expr.offset,
			.right = exprid(right)
		};
		left->resolve_status = RESOLVE_NOT_DONE;
		if (!sema_analyse_expr_rvalue(context, left)) return false;
		expr->pointer_offset_expr.offset = exprid(left);
	}
	else
	{
		// Set the type and other properties.
		expr->type = left->type;
		expr->pointer_offset_expr.raw_offset = false;
		expr->pointer_offset_expr.ptr = exprid(left);
		expr->pointer_offset_expr.offset = exprid(right);
		expr->expr_kind = EXPR_POINTER_OFFSET;
	}

	expr->resolve_status = RESOLVE_NOT_DONE;
	if (!sema_analyse_expr_rvalue(context, expr)) return false;

	if (cast_to_iptr)
	{
		expr->resolve_status = RESOLVE_DONE;
		return cast_explicit(context, expr, cast_to_iptr);
	}
	return true;
}
/**
 * Analyse a + b
 * @return true if it succeeds.
 */
static bool sema_expr_analyse_add(SemaContext *context, Expr *expr, Expr *left, Expr *right, bool *failed_ref)
{
	// 1. Promote everything to the recipient type – if possible
	//    this is safe in the pointer case actually.
	if (!sema_binary_analyse_subexpr(context, left, right)) return false;


	Type *left_type = type_no_optional(left->type)->canonical;
	Type *right_type = type_no_optional(right->type)->canonical;

	if (left_type->type_kind == TYPE_ENUM || right_type->type_kind == TYPE_ENUM)
	{
		return sema_expr_analyse_enum_add_sub(context, expr, left, right, failed_ref);
	}

	// 4. Do a binary arithmetic promotion
	OperatorOverload overload = OVERLOAD_PLUS;
	if (!sema_binary_arithmetic_promotion(context, left, right, left_type, right_type, expr,
		"Cannot do the addition %s + %s.", false, &overload, failed_ref))
	{
		return false;
	}
	if (!overload) return true;

	// 5. Handle the "both const" case. We should only see ints and floats at this point.
	if (expr_both_const_foldable(left, right, BINARYOP_ADD))
	{
		expr_replace(expr, left);
		switch (left->const_expr.const_kind)
		{
			case CONST_INTEGER:
				expr->const_expr.ixx = int_add(left->const_expr.ixx, right->const_expr.ixx);
				break;
			case CONST_FLOAT:
				expr->const_expr.fxx = float_add(left->const_expr.fxx, right->const_expr.fxx);
				break;
			default:
				UNREACHABLE
		}
	}

	// 6. Set the type & other properties.
	expr_binary_unify_failability(expr, left, right);

	return true;

}

/**
 * Analyse a * b
 *
 * Will analyse a and b and then use arithmetic promotion on each.
 * It will then try to promote both to a common type,
 *
 * @return true if analysis worked.
 */
static bool sema_expr_analyse_mult(SemaContext *context, Expr *expr, Expr *left, Expr *right, bool *failed_ref)
{

	// 1. Analyse the sub expressions and promote to a common type
	OperatorOverload overload = OVERLOAD_MULTIPLY;
	if (!sema_binary_analyse_arithmetic_subexpr(context, expr, "It is not possible to multiply %s by %s.", false, &overload, failed_ref)) return false;

	if (!overload) return true;

	// 2. Handle constant folding.
	if (expr_both_const_foldable(left, right, BINARYOP_MULT))
	{
		expr_replace(expr, left);
		switch (left->const_expr.const_kind)
		{
			case CONST_INTEGER:
				expr->const_expr.ixx = int_mul(left->const_expr.ixx, right->const_expr.ixx);
				break;
			case CONST_FLOAT:
				expr->const_expr.fxx = float_mul(left->const_expr.fxx, right->const_expr.fxx);
				break;
			default:
				UNREACHABLE
		}
	}

	// 3. Set the new type
	expr->type = type_add_optional(left->type, IS_OPTIONAL(right));

	return true;
}

/**
 * Analyse a / b
 * @return true if analysis completed ok.
 */
static bool sema_expr_analyse_div(SemaContext *context, Expr *expr, Expr *left, Expr *right, bool *failed_ref)
{
	// 1. Analyse sub expressions and promote to a common type
	OperatorOverload overload = OVERLOAD_DIVIDE;
	if (!sema_binary_analyse_arithmetic_subexpr(context, expr, "Cannot divide %s by %s.", false, &overload, failed_ref)) return false;
	if (!overload) return true;

	// 2. Check for a constant 0 on the rhs.
	if (sema_cast_const(right))
	{
		switch (right->const_expr.const_kind)
		{
			case CONST_INTEGER:
				if (int_is_zero(right->const_expr.ixx))
				{
					RETURN_SEMA_ERROR(right, "This expression evaluates to zero and division by zero is not allowed.");
				}
				break;
			case CONST_FLOAT:
				// This is allowed, as it will generate a NaN
				break;
			case CONST_INITIALIZER:
				// Do not analyse
				break;
			default:
				UNREACHABLE
		}
	}

	// 3. Perform constant folding.
	if (expr_both_const_foldable(left, right, BINARYOP_DIV))
	{
		expr_replace(expr, left);
		switch (left->const_expr.const_kind)
		{
			case CONST_INTEGER:
				expr->const_expr.ixx = int_div(left->const_expr.ixx, right->const_expr.ixx);
				break;
			case CONST_FLOAT:
				expr->const_expr.fxx = float_div(left->const_expr.fxx, right->const_expr.fxx);
				break;
			default:
				UNREACHABLE
		}
	}

	// 4. Done.
	expr->type = type_add_optional(left->type, IS_OPTIONAL(right));
	return true;

}

/**
 * Analyse a % b
 * @return true if analysis succeeds.
 */
static bool sema_expr_analyse_mod(SemaContext *context, Expr *expr, Expr *left, Expr *right, bool *failed_ref)
{
	// 1. Analyse both sides and promote to a common type
	OperatorOverload overload = OVERLOAD_REMINDER;
	if (!sema_binary_analyse_arithmetic_subexpr(context, expr, "Cannot calculate the reminder %s %% %s",
		false, &overload, failed_ref)) return false;
	if (!overload) return true;

	Type *flat = type_flatten(left->type);
	if (type_is_float(flat))
	{
		// 3. a % 0 is not valid, so detect it.
		if (sema_cast_const(right) && right->const_expr.fxx.f == 0.0)
		{
			RETURN_SEMA_ERROR(right, "Cannot perform %% with a constant zero.");
		}

		// 4. Constant fold
		if (expr_both_const_foldable(left, right, BINARYOP_MOD))
		{
			expr_replace(expr, left);
			// 4a. Remember this is remainder.
			expr->const_expr.fxx = float_rem(left->const_expr.fxx, right->const_expr.fxx);
		}
	}
	else if (type_is_integer(flat))
	{
		// 3. a % 0 is not valid, so detect it.
		if (sema_cast_const(right) && int_is_zero(right->const_expr.ixx)) RETURN_SEMA_ERROR(right, "Cannot perform %% with a constant zero.");

		// 4. Constant fold
		if (expr_both_const_foldable(left, right, BINARYOP_MOD))
		{
			expr_replace(expr, left);
			// 4a. Remember this is remainder.
			expr->const_expr.ixx = int_rem(left->const_expr.ixx, right->const_expr.ixx);
		}
	}

	expr->type = type_add_optional(left->type, IS_OPTIONAL(right));
	return true;
}

/**
 * Analyse a ^ b, a | b, a & b
 * @return true if the analysis succeeded.
 */
static bool sema_expr_analyse_bit(SemaContext *context, Expr *expr, Expr *left, Expr *right, OperatorOverload overload, bool *failed_ref)
{
	// 1. Convert to common type if possible.
	if (!sema_binary_analyse_arithmetic_subexpr(context, expr, NULL, true, &overload, failed_ref)) return false;
	if (!overload) return true;

	// 2. Check that both are integers or bools.
	bool is_bool = left->type->canonical == type_bool;
	bool is_bitstruct = left->type->canonical->type_kind == TYPE_BITSTRUCT;

	if (!is_bool && !is_bitstruct && !expr_both_any_integer_or_integer_bool_vector(left, right))
	{
		CHECK_ON_DEFINED(failed_ref);
		return sema_type_error_on_binop(context, expr);
	}

	// 3. Do constant folding if both sides are constant.
	if (expr_both_const_foldable(left, right, BINARYOP_BIT_AND))
	{
		BinaryOp op = expr->binary_expr.operator;
		expr_replace(expr, left);
		if (is_bool)
		{
			switch (op)
			{
				case BINARYOP_BIT_AND:
					expr->const_expr.b = left->const_expr.b & right->const_expr.b;
					break;
				case BINARYOP_BIT_XOR:
					expr->const_expr.b = left->const_expr.b ^ right->const_expr.b;
					break;
				case BINARYOP_BIT_OR:
					expr->const_expr.b = left->const_expr.b | right->const_expr.b;
					break;
				default:
					UNREACHABLE
			}
		}
		else if (is_bitstruct)
		{
			// Avoid merging with value casts, eg (Bitstruct)1
			if (!expr_is_const_initializer(left) || !expr_is_const_initializer(right)) goto DONE;
			ConstInitializer *merged = sema_merge_bitstruct_const_initializers(left->const_expr.initializer,
																			   right->const_expr.initializer, op);
			expr->const_expr.initializer = merged;
		}
		else
		{
			switch (op)
			{
				case BINARYOP_BIT_AND:
					expr->const_expr.ixx = int_and(left->const_expr.ixx, right->const_expr.ixx);
					break;
				case BINARYOP_BIT_XOR:
					expr->const_expr.ixx = int_xor(left->const_expr.ixx, right->const_expr.ixx);
					break;
				case BINARYOP_BIT_OR:
					expr->const_expr.ixx = int_or(left->const_expr.ixx, right->const_expr.ixx);
					break;
				default:
					UNREACHABLE
			}
		}
	}
DONE:
	// 5. Assign the type
	expr_binary_unify_failability(expr, left, right);
	return true;
}

static bool sema_expr_check_shift_rhs(SemaContext *context, Expr *expr, Expr *left,
                                      Type *left_type_flat, Expr *right, Type *right_type_flat, bool *failed_ref,bool is_assign)
{
	// For a constant rhs side we will make a series of checks.
	// We could extend this by checking vectors
	if (sema_cast_const(right) && expr_is_const_int(right))
	{
		// Make sure the value does not exceed the bitsize of
		// the left hand side. We ignore this check for lhs being a constant.


		Type *base = type_vector_base(left_type_flat);
		ASSERT_SPAN(expr, type_kind_is_any_integer(base->type_kind));
		if (int_ucomp(right->const_expr.ixx, base->builtin.bitsize, BINARYOP_GE))
		{
			RETURN_SEMA_ERROR(right, "The shift is not less than the bitsize of %s.", type_quoted_error_string(type_no_optional(left->type)));
		}

		// Make sure that the RHS is positive.
		if (int_is_neg(right->const_expr.ixx))
		{
			RETURN_SEMA_ERROR(right, "A shift must be a positive number.");
		}
	}

	// If LHS is vector but RHS isn't? Promote.
	bool lhs_is_vec = left_type_flat->type_kind == TYPE_VECTOR;
	if (lhs_is_vec && right_type_flat->type_kind != TYPE_VECTOR)
	{
		// Create a vector from the right hand side.
		Type *right_vec = type_get_vector(right->type, left_type_flat->array.len);
		if (!cast_explicit_checkable(context, right, right_vec, failed_ref)) return false;
	}

	bool rhs_is_vec = right_type_flat->type_kind == TYPE_VECTOR;
	if (!lhs_is_vec && rhs_is_vec)
	{
		if (is_assign)
		{
			if (failed_ref) return *failed_ref = true, false;
			RETURN_SEMA_ERROR(right, "The shift cannot be a vector of type %s when shifting a variable of type %s.",
				left->type, right->type);
		}
		Type *left_vec = type_get_vector(left->type, right_type_flat->array.len);
		if (!cast_explicit_checkable(context, left, left_vec, failed_ref)) return false;

	}
	// Same type is always ok
	Type *right_type = type_no_optional(right->type)->canonical;
	if (type_no_optional(left->type)->canonical == right_type) return true;

	Type *base = right_type;
	if (right_type->type_kind == TYPE_VECTOR)
	{
		base = right_type->array.base->canonical;
		base = type_flat_distinct_enum_inline(base);
		right_type = type_get_vector(base, right_type->array.len);
	}
	else
	{
		base = type_flat_distinct_enum_inline(base);
		right_type = base;
	}
	if (!type_is_integer(base))
	{
		if (failed_ref) return *failed_ref = true, false;
		RETURN_SEMA_ERROR(right, "The right hand shift must be an integer type, or match the left hand side type, but it was %s",
			type_quoted_error_string(right->type), type_quoted_error_string(left->type));
	}
	return cast_implicit_binary(context, right, right_type, failed_ref);
}
/**
 * Analyse >> and << operations.
 * @return true if the analysis succeeded.
 */
static bool sema_expr_analyse_shift(SemaContext *context, Expr *expr, Expr *left, Expr *right, bool *failed_ref)
{
	// 1. Analyze both sides.
	if (!sema_binary_analyse_subexpr(context, left, right)) return false;

	Type *lhs_type = type_no_optional(left->type)->canonical;
	bool shr = expr->binary_expr.operator == BINARYOP_SHR;

	if (type_is_user_defined(lhs_type))
	{
		OperatorOverload overload = shr ? OVERLOAD_SHR : OVERLOAD_SHL;
		if (!sema_replace_with_overload(context, expr, left, right, lhs_type, &overload)) return false;
		if (!overload) return true;
	}

	// 3. Promote lhs using the usual numeric promotion.
	if (!cast_implicit_binary(context, left, cast_numeric_arithmetic_promotion(lhs_type), failed_ref)) return false;

	Type *flat_left = type_flatten_and_inline(left->type);
	Type *flat_right = type_flatten_and_inline(right->type);
	if (flat_left->type_kind == TYPE_VECTOR)
	{
		Type *left_base = type_flatten_and_inline(flat_left->array.base);
		if (!type_is_integer(left_base)) goto FAIL;
	}
	else
	{
		if (!type_is_integer(flat_left)) goto FAIL;
	}

	if (!sema_expr_check_shift_rhs(context, expr, left, flat_left, right, flat_right, failed_ref, false)) return false;

	// Fold constant expressions.
	if (expr_both_const_foldable(left, right, BINARYOP_SHL))
	{
		expr_replace(expr, left);
		if (shr)
		{
			expr->const_expr.ixx = int_shr64(left->const_expr.ixx, right->const_expr.ixx.i.low);
		}
		else
		{
			expr->const_expr.ixx = int_shl64(left->const_expr.ixx, right->const_expr.ixx.i.low);
		}
		return true;
	}

	// 6. Set the type
	expr->type = type_add_optional(left->type, IS_OPTIONAL(right));
	return true;
FAIL:
	CHECK_ON_DEFINED(failed_ref);
	return sema_type_error_on_binop(context, expr);
}


static bool sema_expr_analyse_and_or(SemaContext *context, Expr *expr, Expr *left, Expr *right, bool *failed_ref)
{
	if (!sema_binary_analyse_subexpr(context, left, right)) return false;
	if (!cast_explicit_checkable(context, left, type_bool, failed_ref) || !cast_explicit_checkable(context, right, type_bool, failed_ref)) return false;

	if (expr_both_const_foldable(left, right, BINARYOP_AND))
	{
		if (expr->binary_expr.operator == BINARYOP_AND)
		{
			expr_replace(expr, left);
			expr->const_expr.b &= right->const_expr.b;
		}
		else
		{
			expr_replace(expr, left);
			expr->const_expr.b |= right->const_expr.b;
		}
	}
	expr->type = type_add_optional(type_bool, IS_OPTIONAL(left) || IS_OPTIONAL(right));
	return true;
}

static bool sema_binary_is_unsigned_always_same_comparison(SemaContext *context, Expr *expr, Expr *left, Expr *right,
														   Type *lhs_type, Type *rhs_type)
{
	if (context->active_scope.flags & (SCOPE_MACRO | SCOPE_ENSURE | SCOPE_ENSURE_MACRO)) return true;
	if (!sema_cast_const(left) && !sema_cast_const(right)) return true;
	if (!type_is_integer(left->type)) return true;
	if (expr_is_const_int(left) && type_is_unsigned(rhs_type))
	{
		if (int_is_neg(left->const_expr.ixx))
		{
			SEMA_ERROR(left, "Comparing an unsigned value with a negative constant is only allowed inside of macros.");
			return false;
		}
		if (!int_is_zero(left->const_expr.ixx)) return true;
		switch (expr->binary_expr.operator)
		{
			case BINARYOP_GT:
				RETURN_SEMA_ERROR(right,
								  "Comparing '0 > unsigned expression' can never be true, and is only allowed inside of macro expansions.");
				return false;
			case BINARYOP_GE:
				RETURN_SEMA_ERROR(right,
								  "Comparing '0 >= unsigned expression' is the same as 0 == expr and is a common bug, "
								  "for this reason it is only allowed inside of macro expansions.");
			case BINARYOP_LE:
				RETURN_SEMA_ERROR(right,
								  "Comparing '0 <= unsigned expression' is always true and is a common bug, "
								  "for this reason it is only allowed inside of macro expansions.");
			default:
				return true;
		}
	}
	if (!expr_is_const_int(right) || !type_is_unsigned(lhs_type)) return true;
	if (int_is_neg(right->const_expr.ixx))
	{
		SEMA_ERROR(right, "Comparing an unsigned value with a negative constant is only allowed inside of macros.");
		return false;
	}
	if (!int_is_zero(right->const_expr.ixx)) return true;
	switch (expr->binary_expr.operator)
	{
		case BINARYOP_LT:
			RETURN_SEMA_ERROR(right,
							  "Comparing 'unsigned expression < 0' can never be true, and is only allowed inside of macro expansions.");
			return false;
		case BINARYOP_LE:
			RETURN_SEMA_ERROR(right,
							  "Comparing 'unsigned expression <= 0' is the same as expr == 0 and is a common bug, "
							  "for this reason it is only allowed inside of macro expansions.");
		case BINARYOP_GE:
			RETURN_SEMA_ERROR(right,
							  "Comparing 'unsigned expression >= 0' is always true and is a common bug, "
							  "for this reason it is only allowed inside of macro expansions.");
		default:
			return true;
	}

}

BoolErr sema_type_has_equality_overload(SemaContext *context, CanonicalType *type)
{
	assert(type->canonical == type);
	Decl *candidate = NULL;
	Decl *ambiguous = NULL;
	OverloadMatch match = sema_find_typed_operator_type(context, OVERLOAD_EQUAL, OVERLOAD_TYPE_LEFT, type, type, NULL, &candidate, OVERLOAD_MATCH_NONE, &ambiguous);
	if (match == OVERLOAD_MATCH_NONE) match = sema_find_typed_operator_type(context, OVERLOAD_NOT_EQUAL, OVERLOAD_TYPE_LEFT, type, type, NULL, &candidate, OVERLOAD_MATCH_NONE, &ambiguous);
	switch (match)
	{
		case OVERLOAD_MATCH_ERROR:
			return BOOL_ERR;
		case OVERLOAD_MATCH_EXACT:
		case OVERLOAD_MATCH_WILDCARD:
		case OVERLOAD_MATCH_CONVERSION:
			return BOOL_TRUE;
		case OVERLOAD_MATCH_NONE:
		case OVERLOAD_MATCH_AMBIGUOUS_WILDCARD:
		case OVERLOAD_MATCH_AMBIGUOUS_CONVERSION:
		case OVERLOAD_MATCH_AMBIGUOUS_EXACT:
			return BOOL_FALSE;
	}
	UNREACHABLE
}

BoolErr sema_type_can_check_equality_with_overload(SemaContext *context, Type *type)
{
	RETRY:
	switch (type->type_kind)
	{
		case TYPE_INFERRED_VECTOR:
		case TYPE_INFERRED_ARRAY:
		case TYPE_POISONED:
			UNREACHABLE
		case TYPE_VOID:
		case TYPE_FLEXIBLE_ARRAY:
		case TYPE_OPTIONAL:
		case TYPE_MEMBER:
		case TYPE_UNTYPED_LIST:
			return false;
		case TYPE_UNION:
		case TYPE_STRUCT:
			if (type->decl->attr_compact && compiler.build.old_compact_eq) return true;
			return sema_type_has_equality_overload(context, type);
		case TYPE_BITSTRUCT:
			return true;
		case TYPE_ALIAS:
			type = type->canonical;
			goto RETRY;
		case TYPE_SLICE:
		case TYPE_ARRAY:
			// Arrays are comparable if elements are
			type = type->array.base;
			goto RETRY;
		case TYPE_TYPEDEF:
		case TYPE_CONST_ENUM:
			if (sema_type_has_equality_overload(context, type)) return true;
			type = type_inline(type);
			goto RETRY;
		case TYPE_BOOL:
		case ALL_INTS:
		case ALL_FLOATS:
		case TYPE_ANY:
		case TYPE_INTERFACE:
		case TYPE_ANYFAULT:
		case TYPE_TYPEID:
		case TYPE_POINTER:
		case TYPE_ENUM:
		case TYPE_FUNC_PTR:
		case TYPE_FUNC_RAW:
		case TYPE_TYPEINFO:
		case TYPE_VECTOR:
		case TYPE_WILDCARD:
			return true;
	}
	UNREACHABLE
}

INLINE Decl *ast_append_generated_local(Ast **ast_current_ref, Expr *init)
{
	Ast *ast = ast_new(AST_DECLARE_STMT, init->span);
	Decl *var = decl_new_generated_var(init->type, VARDECL_LOCAL, init->span);
	assert(init->resolve_status == RESOLVE_DONE);
	var->var.init_expr = init;
	ast->declare_stmt = var;
	(*ast_current_ref)->next = astid(ast);
	*ast_current_ref = ast;
	return var;
}

static inline bool sema_rewrite_expr_as_macro_block(SemaContext *context, Expr *expr, AstId start)
{
	Type *old_expected_block = context->expected_block_type;
	BlockExit **old_exit_ref = context->block_exit_ref;
	context->expected_block_type = type_bool;
	BlockExit** block_exit_ref = CALLOCS(BlockExit*);
	context->block_exit_ref = block_exit_ref;
	bool success;
	Ast *compound_stmt = ast_new(AST_COMPOUND_STMT, expr->span);
	compound_stmt->compound_stmt.first_stmt = start;
	SCOPE_START_WITH_FLAGS(SCOPE_MACRO)
		success = sema_analyse_stmt_chain(context, compound_stmt);
	SCOPE_END;
	context->expected_block_type = old_expected_block;
	context->block_exit_ref = old_exit_ref;

	if (!success) return false;
	expr->expr_kind = EXPR_MACRO_BLOCK;
	expr->resolve_status = RESOLVE_DONE;
	expr->type = type_bool;
	expr->macro_block = (ExprMacroBlock) {
		.first_stmt = astid(compound_stmt),
		.block_exit = block_exit_ref
	};
	return true;
}
static bool sema_rewrite_slice_comparison(SemaContext *context, Expr *expr, Expr *left, Expr *right, Type *max)
{
	Ast dummy;
	Ast *current = &dummy;
	Decl *left_var = left->expr_kind == EXPR_IDENTIFIER ? left->ident_expr : ast_append_generated_local(&current, left);
	Decl *right_var = right->expr_kind == EXPR_IDENTIFIER ? right->ident_expr : ast_append_generated_local(&current, right);
	Decl *len_var_left = NULL;
	ArraySize len = 0;
	SourceSpan default_span = expr->span;
	if (max->type_kind == TYPE_ARRAY)
	{
		len = max->array.len;
	}
	else
	{
		Expr *len_left = expr_new(EXPR_SLICE_LEN, left->span);
		Expr *len_right = expr_new(EXPR_SLICE_LEN, right->span);
		len_left->inner_expr = expr_variable(left_var);
		len_right->inner_expr = expr_variable(right_var);
		len_left->type = type_usz;
		len_right->type = type_usz;
		if (!sema_analyse_expr_rvalue(context, len_left)) return false;
		if (!sema_analyse_expr_rvalue(context, len_right)) return false;
		len_var_left = ast_append_generated_local(&current, len_left);
		Ast *ast_if = ast_new(AST_IF_STMT, default_span);
		Expr *expr_comparison = expr_new_binary(default_span, expr_variable(len_var_left), len_right, BINARYOP_NE);
		Ast *ast_then = ast_new(AST_RETURN_STMT, default_span);
		ast_then->return_stmt.expr = expr_new_const_bool(default_span, type_bool, false);
		ast_if->if_stmt = (AstIfStmt) {
			.cond = exprid(expr_new_cond(expr_comparison)),
			.then_body = astid(ast_then),
		};
		current->next = astid(ast_if);
		current = ast_if;
	}
	Decl *index = ast_append_generated_local(&current, expr_new_const_int(default_span, type_usz, 0));
	Ast *ast = ast_new(AST_FOR_STMT, default_span);
	Expr *cond_expr;
	if (len > 0)
	{
		cond_expr = expr_new_binary(default_span, expr_variable(index), expr_new_const_int(default_span, type_usz, len), BINARYOP_LT);
	}
	else
	{
		cond_expr = expr_new_binary(default_span, expr_variable(index), expr_variable(len_var_left), BINARYOP_LT);
	}
	ast->for_stmt.cond = exprid(expr_new_cond(cond_expr));
	Expr *update = expr_new(EXPR_UNARY, default_span);
	update->unary_expr.expr = expr_variable(index);
	update->unary_expr.operator = UNARYOP_INC;
	update->unary_expr.no_wrap = true;
	ast->for_stmt.incr = exprid(update);

	Expr *left_check = expr_new(EXPR_SUBSCRIPT, default_span);
	left_check->subscript_expr.expr = exprid(expr_variable(left_var));
	left_check->subscript_expr.index.expr = exprid(expr_variable(index));
	left_check->subscript_expr.no_check = true;
	Expr *right_check = expr_new(EXPR_SUBSCRIPT, default_span);
	right_check->subscript_expr.expr = exprid(expr_variable(right_var));
	right_check->subscript_expr.index.expr = exprid(expr_variable(index));
	right_check->subscript_expr.no_check = true;

	Expr *expr_comparison = expr_new_binary(default_span, left_check, right_check, BINARYOP_NE);
	Ast *ast_then = ast_new(AST_RETURN_STMT, default_span);
	ast_then->return_stmt.expr = expr_new_const_bool(default_span, type_bool, false);
	Ast *ast_if = ast_new(AST_IF_STMT, default_span);
	ast_if->if_stmt = (AstIfStmt) {
		.cond = exprid(expr_new_cond(expr_comparison)),
		.then_body = astid(ast_then),
	};
	ast->for_stmt.body = astid(ast_if);

	current->next = astid(ast);
	current = ast;
	Ast *ast_after = ast_new(AST_RETURN_STMT, default_span);
	ast_after->return_stmt.expr = expr_new_const_bool(default_span, type_bool, true);
	current->next = astid(ast_after);

	return sema_rewrite_expr_as_macro_block(context, expr, dummy.next);
}

/**
 * Analyze a == b, a != b, a > b, a < b, a >= b, a <= b
 * @return
 */
static bool sema_expr_analyse_comp(SemaContext *context, Expr *expr, Expr *left, Expr *right, bool *failed_ref)
{
	// 1. Analyse left and right side without any conversions.
	if (!sema_binary_analyse_subexpr(context, left, right)) return false;

	bool is_equality_type_op = expr->binary_expr.operator == BINARYOP_NE || expr->binary_expr.operator == BINARYOP_EQ;

	Type *left_type = type_no_optional(left->type)->canonical;
	Type *right_type = type_no_optional(right->type)->canonical;

	if (is_equality_type_op && (type_is_user_defined(left_type) || type_is_user_defined(right_type)))
	{
		Decl *overload = NULL;
		bool negated_overload = false;
		bool reverse = false;
		switch (expr->binary_expr.operator)
		{
			case BINARYOP_NE:
				overload = sema_find_typed_operator(context, OVERLOAD_NOT_EQUAL, expr->span, left, right, &reverse);
				if (!overload)
				{
					negated_overload = true;
					overload = sema_find_typed_operator(context, OVERLOAD_EQUAL, expr->span, left, right, &reverse);
				}
				if (!decl_ok(overload)) return false;
				if (!overload) goto NEXT;
				break;
			case BINARYOP_EQ:
				overload = sema_find_typed_operator(context, OVERLOAD_EQUAL, expr->span, left, right, &reverse);
				if (!overload)
				{
					negated_overload = true;
					overload = sema_find_typed_operator(context, OVERLOAD_NOT_EQUAL, expr->span, left, right, &reverse);
				}
				if (!decl_ok(overload)) return false;
				if (!overload) goto NEXT;
				break;
			default:
				UNREACHABLE
		}
		Expr **args = NULL;
		Decl *first_param = overload->func_decl.signature.params[1];
		if (first_param->type && first_param->type->canonical->type_kind == TYPE_POINTER)
		{
			expr_insert_addr(right);
		}
		vec_add(args, right);
		if (!sema_insert_method_call(context, expr, overload, left, args, reverse)) return false;
		if (!negated_overload) return true;
		assert(expr->resolve_status == RESOLVE_DONE);
		Expr *inner = expr_copy(expr);
		expr->expr_kind = EXPR_UNARY;
		expr->unary_expr = (ExprUnary) { .expr = inner, .operator = UNARYOP_NOT };
		expr->resolve_status = RESOLVE_NOT_DONE;
		return sema_analyse_expr_rvalue(context, expr);
	}
NEXT:
	// Flatten distinct/optional
	left_type = type_flat_distinct_inline(left_type)->canonical;
	right_type = type_flat_distinct_inline(right_type)->canonical;

	// 2. Handle the case of signed comparisons.
	//    This happens when either side has a definite integer type
	//    and those are either signed or unsigned.
	//    If either side is compint, then this does not happen.
	if ((type_is_unsigned(left_type) && type_is_signed(right_type))
		|| (type_is_signed(left_type) && type_is_unsigned(right_type)))
	{
		// 2a. Resize so that both sides have the same bit width. This will always work.
		cast_to_int_to_max_bit_size(left, right, left_type, right_type);
		goto DONE;
	}


	// 3. In the normal case, treat this as a binary op, finding the max type.
	Type *max = type_find_max_type(left_type, right_type, left, right);

	// 4. If no common type, then that's an error:
	if (!max)
	{
		CHECK_ON_DEFINED(failed_ref);
		RETURN_SEMA_ERROR(expr, "%s and %s are different types and cannot be compared.",
						  type_quoted_error_string(left->type), type_quoted_error_string(right->type));
	}

	max = max->canonical;

	if (max->type_kind == TYPE_VECTOR && !is_equality_type_op)
	{
		RETURN_SEMA_ERROR(expr, "Vector types can only be tested for equality, for other comparison, use vector comparison functions.");
	}

	if (max == type_untypedlist)
	{
		RETURN_SEMA_ERROR(expr, "Both sides are untyped and cannot be compared. Please cast one or both sides to a type, e.g. (Foo){ 1, 2 } == { 1, 2 }.");
	}

	if (!is_equality_type_op)
	{
		if (!type_is_ordered(max))
		{
			CHECK_ON_DEFINED(failed_ref);
			RETURN_SEMA_ERROR(expr, "%s can only be compared using '!=' and '==' it "
									"cannot be ordered, did you make a mistake?",
							  type_quoted_error_string(left->type));
		}

		if (type_is_pointer_type(max))
		{

			// Only comparisons between the same type is allowed. Subtypes not allowed.
			if (left_type != right_type && left_type != type_voidptr && right_type != type_voidptr)
			{
				RETURN_SEMA_ERROR(expr, "You are not allowed to compare pointers of different types, "
										"if you need to do, first convert all pointers to void*.");
			}
		}
	}

	// 6. Do the explicit cast.
	if (!cast_implicit_checked(context, left, max, false, failed_ref) || !cast_implicit_checked(context, right, max, false, failed_ref)) return false;
	bool success = cast_explicit_checkable(context, left, max, failed_ref) && cast_explicit_checkable(context, right, max, failed_ref);
	ASSERT_SPAN(expr, success);

	if (!type_is_comparable(max))
	{
		if (is_equality_type_op && (max->type_kind == TYPE_SLICE || max->type_kind == TYPE_ARRAY))
		{
			Type *base = max->array.base;
			switch (sema_type_has_equality_overload(context, base))
			{
				case BOOL_ERR:
					return false;
				case BOOL_FALSE:
					break;
				case BOOL_TRUE:
					return sema_rewrite_slice_comparison(context, expr, left, right, max);
			}
		}

		CHECK_ON_DEFINED(failed_ref);
		RETURN_SEMA_ERROR(expr, "%s does not support comparisons.",
						  type_quoted_error_string(left->type));
	}

DONE:

	// 7. Do constant folding.
	if (expr_both_const_foldable(left, right, BINARYOP_EQ))
	{
		expr->const_expr.b = expr_const_compare(&left->const_expr, &right->const_expr, expr->binary_expr.operator);
		expr->const_expr.const_kind = CONST_BOOL;
		expr->expr_kind = EXPR_CONST;
		expr->resolve_status = RESOLVE_DONE;
	}
	else
	{
		if (!sema_binary_is_unsigned_always_same_comparison(context, expr, left, right, left_type, right_type)) return false;
	}

	// 8. Set the type to bool
	expr->type = type_add_optional(type_bool, IS_OPTIONAL(left) || IS_OPTIONAL(right));

	return true;

}

/**
 * Analyse *a
 * @return true if analysis succeeds.
 */
static inline bool sema_expr_analyse_deref(SemaContext *context, Expr *expr, bool *failed_ref)
{
	// 1. Check the inner expression
	Expr *inner = expr->unary_expr.expr;
	if (!sema_analyse_expr_rvalue(context, inner)) return false;

	Type *inner_type_nofail = type_no_optional(inner->type);
	Type *canonical = inner_type_nofail->canonical;

	// 2. Check that we have a pointer, or dereference is not allowed.
	if (canonical->type_kind != TYPE_POINTER)
	{
		if (failed_ref) goto ON_FAILED;
		RETURN_SEMA_ERROR(inner, "Cannot dereference a value of type %s, it must be a pointer.",
						  type_quoted_error_string(inner_type_nofail));
	}
	if (type_is_void(canonical->pointer))
	{
		if (failed_ref) goto ON_FAILED;
		RETURN_SEMA_ERROR(inner, "A 'void*' cannot be dereferenced, you need to first cast it to a concrete type.");
	}

	// 3. This could be a constant, in which case we can inline any *&foo to "foo"
	//    and check for null.
	if (sema_cast_const(inner))
	{
		switch (inner->const_expr.const_kind)
		{
			case CONST_POINTER:
				if (!inner->const_expr.ptr)
				{
					if (failed_ref) goto ON_FAILED;
					RETURN_SEMA_ERROR(inner, "Dereferencing null is not allowed, did you do it by mistake?");
				}
				break;
			case CONST_REF:
				expr_replace(expr, expr_variable(inner->const_expr.global_ref));
				break;
			case CONST_BYTES:
			case CONST_STRING:
				expr_rewrite_const_int(expr, type_get_indexed_type(inner->type), inner->const_expr.bytes.len ? inner->const_expr.bytes.ptr[0] : 0);
				return true;
			default:
				UNREACHABLE
		}
	}

	// 4. Now the type might not be a pointer because of a typedef,
	//    otherwise we need to use the canonical representation.
	Type *deref_type = inner_type_nofail->type_kind == TYPE_POINTER ? inner_type_nofail : canonical;

	// 5. And... set the type.
	expr->type = type_add_optional(deref_type->pointer, IS_OPTIONAL(inner));
	return true;
ON_FAILED:
	*failed_ref = true;
	return false;
}

static inline const char *sema_addr_may_take_of_var(Expr *expr, Decl *decl)
{
	if (decl->decl_kind != DECL_VAR) return "This is not a regular variable.";
	decl->var.is_addr = true;
	bool is_void = type_flatten(decl->type) == type_void;
	switch (decl->var.kind)
	{
		case VARDECL_GLOBAL:
			if (is_void) return "You cannot take the address of a global of type 'void'";
			return NULL;
		case VARDECL_LOCAL:
			if (is_void) return "You cannot take the address of a variable of type 'void'";
			return NULL;
		case VARDECL_PARAM:
			if (is_void) return "You cannot take the address of a parameter of type 'void'";
			return NULL;
		case VARDECL_CONST:
			if (!decl->var.type_info)
			{
				return "The constant is not typed, either type it or use && to take the reference to a temporary.";
			}
			ASSERT_SPAN(expr, decl->type != type_void);
			return NULL;
		case VARDECL_PARAM_EXPR:
			return "It is not possible to take the address of a captured expression, but you can use && to take a reference to the temporary value.";
		case VARDECL_PARAM_CT:
		case VARDECL_PARAM_CT_TYPE:
		case VARDECL_LOCAL_CT_TYPE:
		case VARDECL_LOCAL_CT:
			// May not be reached due to EXPR_CT_IDENT being handled elsewhere.
			UNREACHABLE
		case VARDECL_MEMBER:
		case VARDECL_BITMEMBER:
		case VARDECL_UNWRAPPED:
		case VARDECL_REWRAPPED:
		case VARDECL_ERASE:
			UNREACHABLE
	}
	UNREACHABLE

}

static inline const char *sema_addr_may_take_of_ident(Expr *inner)
{
	Decl *decl = decl_raw(inner->ident_expr);
	switch (decl->decl_kind)
	{
		case DECL_POISONED:
			expr_poison(inner);
			return NULL;
		case DECL_FUNC:
			if (decl->func_decl.attr_test)
			{
				return "You may not take the address of a '@test' function.";
			}
			if (decl->func_decl.attr_benchmark)
			{
				return "You may not take the address of a '@benchmark' function.";
			}
			return NULL;
		case DECL_VAR:
			return sema_addr_may_take_of_var(inner, decl);
		case DECL_MACRO:
			return "It is not possible to take the address of a macro.";
		case DECL_LABEL:
			return "It is not possible to take the address of a label.";
		default:
			UNREACHABLE
	}
	UNREACHABLE
}

static const char *sema_addr_check_may_take(Expr *inner)
{
	switch (inner->expr_kind)
	{
		case UNRESOLVED_EXPRS:
			UNREACHABLE
		case EXPR_CT_IDENT:
			return "It's not possible to take the address of a compile time value.";
		case EXPR_IDENTIFIER:
			return sema_addr_may_take_of_ident(inner);
		case EXPR_UNARY:
			if (inner->unary_expr.operator == UNARYOP_DEREF) return NULL;
			break;
		case EXPR_ACCESS_RESOLVED:
		{
			Decl *decl = inner->access_resolved_expr.ref;
			switch (decl->decl_kind)
			{
				case DECL_FUNC:
					if (decl->func_decl.attr_interface_method) return NULL;
					return "Taking the address of a method should be done through the type e.g. '&Foo.method' not through the value.";
				case DECL_MACRO:
					return "It's not possible to take the address of a macro.";
				default:
					return sema_addr_check_may_take(inner->access_resolved_expr.parent);
			}
		}
		case EXPR_SUBSCRIPT_ADDR:
			return NULL;
		case EXPR_SUBSCRIPT:
			return sema_addr_check_may_take(exprptr(inner->subscript_expr.expr));
		case EXPR_TYPEINFO:
			return "It is not possible to take the address of a type.";
		case EXPR_BITACCESS:
			return "You cannot take the address of a bitstruct member.";
		default:
			break;
	}
	return "To take the address of a temporary value, use '&&' instead of '&'.";
}

/**
 * Analyse &a
 * @return true if analysis succeeds.
 */
static inline bool sema_expr_analyse_addr(SemaContext *context, Expr *expr, bool *failed_ref)
{
RETRY:;
	// 1. Evaluate the expression
	Expr *inner = expr->unary_expr.expr;
	if (inner->resolve_status == RESOLVE_DONE) goto RESOLVED;
	switch (inner->expr_kind)
	{
		case EXPR_POISONED:
			return false;
		case EXPR_OTHER_CONTEXT:
		{
			Expr *inner_c = inner->expr_other_context.inner;
			SemaContext *c2 = inner->expr_other_context.context;
			bool is_ref = inner->expr_other_context.is_ref = true;
			expr_replace(inner, inner_c);
			if (is_ref) expr_set_to_ref(inner);
			return sema_expr_analyse_addr(c2, expr, failed_ref);
		}
		case EXPR_HASH_IDENT:
			if (!sema_expr_fold_hash(context, inner)) return false;
			goto RETRY;
		case EXPR_SUBSCRIPT:
			inner->expr_kind = EXPR_SUBSCRIPT_ADDR;
			if (failed_ref)
			{
				if (!sema_expr_analyse_subscript(context, inner, failed_ref)) return false;
			}
			if (!sema_analyse_expr(context, inner)) return false;
			expr_replace(expr, inner);
			return true;
		case EXPR_ACCESS_UNRESOLVED:
			inner->access_unresolved_expr.is_ref = true;
			break;
		default:
			break;
	}
RESOLVED:
	if (inner->expr_kind == EXPR_CT_IDENT)
	{
		RETURN_SEMA_ERROR(expr, "It's not possible to take the address of a compile time value.");
	}

	if (!sema_analyse_expr(context, inner)) return false;

	// 2. Take the address.
	const char *error = sema_addr_check_may_take(inner);
	if (error)
	{
		if (failed_ref)
		{
			*failed_ref = true;
			return false;
		}
		RETURN_SEMA_ERROR(inner, error);
	}

	Type *no_optional = type_no_optional(inner->type)->canonical;
	// 3. Get the pointer of the underlying type.
	if (no_optional->type_kind == TYPE_FUNC_RAW)
	{
		expr->type = type_add_optional(type_get_func_ptr(no_optional), IS_OPTIONAL((inner)));
	}
	else
	{
		expr->type = type_get_ptr_recurse(inner->type);
	}
	if (inner->expr_kind == EXPR_IDENTIFIER)
	{
		Decl *ident = inner->ident_expr;
		if (decl_is_global(ident))
		{
			expr_rewrite_const_ref(expr, ident);
			return true;
		}
	}
	return true;
}

/**
 * Test -a / +a
 */
static inline bool sema_expr_analyse_neg_plus(SemaContext *context, Expr *expr)
{
	// 1. Check the inner expression
	Expr *inner = expr->unary_expr.expr;
	bool is_plus = expr->unary_expr.operator == UNARYOP_PLUS;
	if (!sema_analyse_expr_rvalue(context, inner)) return false;

	// 2. Check if it's possible to negate this (i.e. is it an int, float or vector)
	Type *no_fail = type_no_optional(inner->type);
	Type *canonical = no_fail->canonical;

	// Check for overload
	if (type_is_user_defined(canonical))
	{
		Decl *overload = sema_find_untyped_operator(canonical, OVERLOAD_UNARY_MINUS, NULL);
		if (overload)
		{
			// Plus just returns inner
			if (is_plus)
			{
				expr_replace(expr, inner);
				return true;
			}
			return sema_insert_method_call(context, expr, overload, inner, NULL, false);
		}
	}
	if (!type_may_negate(no_fail))
	{
		if (is_plus)
		{
			RETURN_SEMA_ERROR(expr, "Cannot use '+' with an expression of type %s.", type_quoted_error_string(no_fail));
		}
		RETURN_SEMA_ERROR(expr, "Cannot negate an expression of type %s.", type_quoted_error_string(no_fail));
	}
	// 3. Promote the type
	Type *result_type = cast_numeric_arithmetic_promotion(no_fail);
	if (!cast_implicit(context, inner, result_type, false)) return false;

	// If it's a plus, we simply replace the inner with the outer or with a recast.
	if (is_plus)
	{
		if (expr_is_const(expr))
		{
			expr_replace(expr, inner);
			return true;
		}
		expr->expr_kind = EXPR_RECAST;
		expr->inner_expr = inner;
		expr->type = inner->type;
		return true;
	}
	// 4. If it's non-const, we're done.
	if (!expr_const_foldable_unary(inner, UNARYOP_NEG))
	{
		expr->type = inner->type;
		return true;
	}

	// 5. Otherwise constant fold.
	expr_replace(expr, inner);
	switch (expr->const_expr.const_kind)
	{
		case CONST_INTEGER:
			expr->const_expr.ixx = int_neg(inner->const_expr.ixx);
			return true;
		case CONST_FLOAT:
			expr->const_expr.fxx = float_neg(expr->const_expr.fxx);
			break;
		default:
			UNREACHABLE
	}
	return true;
}

/**
 * Analyse ~x and ~123
 *
 * @return
 */
static inline bool sema_expr_analyse_bit_not(SemaContext *context, Expr *expr, bool *failed_ref)
{
	// 1. Analyse the inner expression.
	Expr *inner = expr->unary_expr.expr;
	if (!sema_analyse_expr_rvalue(context, inner)) return false;

	// 2. Check that it's a vector, bool
	Type *canonical = type_no_optional(inner->type)->canonical;

	if (type_is_user_defined(canonical) && canonical->type_kind != TYPE_BITSTRUCT)
	{
		Decl *overload = sema_find_untyped_operator(canonical, OVERLOAD_NEGATE, NULL);
		if (overload) return sema_insert_method_call(context, expr, overload, inner, NULL, false);
	}

	Type *flat = type_flatten(canonical);
	bool is_bitstruct = flat->type_kind == TYPE_BITSTRUCT;
	if (!type_is_integer_or_bool_kind(flat) && !is_bitstruct)
	{
		Type *vector_type = type_vector_type(canonical);
		if (vector_type && (type_is_integer(vector_type) || vector_type == type_bool)) goto VALID_VEC;
		CHECK_ON_DEFINED(failed_ref);
		RETURN_SEMA_ERROR(expr, "Cannot bit negate '%s'.", type_to_error_string(inner->type));
	}

VALID_VEC:
	if (is_bitstruct && sema_cast_const(inner) && expr_is_const_initializer(inner))
	{
		expr_replace(expr, inner);
		sema_invert_bitstruct_const_initializer(expr->const_expr.initializer);
		return true;
	}

	// Arithmetic promotion
	Type *result_type = cast_numeric_arithmetic_promotion(type_no_optional(inner->type));
	if (!cast_implicit_checked(context, inner, result_type, false, failed_ref)) return false;

	// 3. The simple case, non-const.
	if (!expr_const_foldable_unary(inner, UNARYOP_BITNEG))
	{
		expr->type = inner->type;
		return true;
	}

	// 4. Otherwise handle const bool
	expr_replace(expr, inner);
	if (expr->const_expr.const_kind == CONST_BOOL)
	{
		expr->const_expr.b = !expr->const_expr.b;
		return true;
	}

	// 5. Perform ~ constant folded
	expr->const_expr.ixx = int_not(inner->const_expr.ixx);
	return true;
}

/**
 * Evaluate !a
 */
static inline bool sema_expr_analyse_not(SemaContext *context, Expr *expr)
{
	// 1. Evaluate inner
	Expr *inner = expr->unary_expr.expr;
	if (!sema_analyse_expr_rvalue(context, inner)) return false;

	// 2. Check whether the type is a vector
	Type *type = type_no_optional(inner->type);
	Type *flat = type_flatten(type);
	if (type_kind_is_any_vector(flat->type_kind))
	{
		// This may be some form of bool vector.
		if (type_flatten(flat->array.base) == type_bool)
		{
			// If so then we're done.
			expr->type = type;
			return true;
		}
		Type *canonical = type->canonical;
		switch (canonical->type_kind)
		{
			case TYPE_VECTOR:
				expr->type = type_get_vector(type_bool, canonical->array.len);
				return true;
			case TYPE_INFERRED_VECTOR:
				UNREACHABLE
			default:
				break;
		}
	}

	if (!cast_explicit_silent(context, inner, type_add_optional(type_bool, IS_OPTIONAL(inner))))
	{
		RETURN_SEMA_ERROR(expr, "The %s can't be converted to a boolean value.", type_quoted_error_string(inner->type));
	}

	expr->type = type_add_optional(type_bool, IS_OPTIONAL(inner));

	if (inner->expr_kind == EXPR_INT_TO_BOOL)
	{
		inner->int_to_bool_expr.negate = !inner->int_to_bool_expr.negate;
		expr_replace(expr, inner);
		return true;
	}

	if (sema_cast_const(inner))
	{
		ASSERT_SPAN(expr, expr_const_foldable_unary(inner, UNARYOP_NOT));
		ASSERT_SPAN(expr, inner->const_expr.const_kind == CONST_BOOL);
		expr->const_expr.const_kind = CONST_BOOL;
		expr->expr_kind = EXPR_CONST;
		expr->resolve_status = RESOLVE_DONE;
		expr->const_expr.b = !inner->const_expr.b;
		return true;
	}

	return true;
}

static inline bool sema_expr_analyse_ct_subscript_incdec(SemaContext *context, Expr *expr, Expr *inner)
{
	Decl *ct_var = inner->ct_subscript_expr.var;
	Expr *init = ct_var->var.init_expr;
	ArrayIndex index = inner->ct_subscript_expr.index;
	bool post = expr->expr_kind == EXPR_POST_UNARY;
	bool dec = expr->unary_expr.operator == UNARYOP_DEC;
	ASSIGN_EXPR_OR_RET(Expr *value, expr_from_const_expr_at_index(init, index), false);
	if (!expr_is_const_int(value))
	{
		RETURN_SEMA_ERROR(expr, "The indexed type is not an integer.");
	}
	if (post)
	{
		expr_replace(expr, copy_expr_single(value));
	}
	if (dec)
	{
		value->const_expr.ixx = int_sub64(value->const_expr.ixx, 1);
	}
	else
	{
		value->const_expr.ixx = int_add64(value->const_expr.ixx, 1);
	}
	if (!post)
	{
		expr_replace(expr, value);
	}
	sema_expr_analyse_ct_subscript_set_value(inner, ct_var, value);
	return true;
}

static inline bool sema_expr_analyse_ct_incdec(SemaContext *context, Expr *expr, Expr *inner)
{
	ASSERT_SPAN(expr, inner->expr_kind == EXPR_CT_IDENT);

	Decl *var = inner->ct_ident_expr.decl;
	Expr *start_value = var->var.init_expr;
	if (!expr_is_const_int(start_value))
	{
		RETURN_SEMA_ERROR(expr, "The compile time variable '%s' does not hold an integer.", var->name);
	}
	Expr *end_value = expr_copy(start_value);

	// Make the change.
	if (expr->unary_expr.operator == UNARYOP_DEC)
	{
		end_value->const_expr.ixx = int_sub64(start_value->const_expr.ixx, 1);
	}
	else
	{
		end_value->const_expr.ixx = int_add64(start_value->const_expr.ixx, 1);
	}
	var->var.init_expr = end_value;
	if (expr->expr_kind == EXPR_POST_UNARY)
	{
		expr_replace(expr, start_value);
	}
	else
	{
		expr_replace(expr, end_value);
	}
	return true;
}

static bool sema_analyse_assign_mutate_overloaded_subscript(SemaContext *context, Expr *main, Expr *subscript_expr, Type *type)
{
	Expr *increased = exprptr(subscript_expr->subscript_assign_expr.expr);
	Type *type_check = increased->type->canonical;
	Decl *operator = sema_find_untyped_operator(type_check, OVERLOAD_ELEMENT_REF, NULL);
	Expr **args = NULL;
	// The simple case: we have &[] so just replace it by that.
	if (operator)
	{
		vec_add(args, exprptr(subscript_expr->subscript_assign_expr.index));
		if (!sema_insert_method_call(context, subscript_expr, operator, exprptr(subscript_expr->subscript_assign_expr.expr), args, false)) return false;
		if (!sema_expr_rewrite_insert_deref(context, subscript_expr)) return false;
		main->type = subscript_expr->type;
		return true;
	}
	// We need []= and [] now.
	operator = sema_find_untyped_operator(type_check, OVERLOAD_ELEMENT_AT, NULL);
	if (!operator)
	{
		RETURN_SEMA_ERROR(main, "There is no overload for [] for %s.", type_quoted_error_string(increased->type));
	}
	Type *return_type = typeget(operator->func_decl.signature.rtype);
	if (type_no_optional(return_type->canonical) != type->canonical)
	{
		RETURN_SEMA_ERROR(main, "There is a type mismatch between overload for [] and []= for %s.", type_quoted_error_string(increased->type));
	}
	bool is_optional_result = type_is_optional(increased->type) || type_is_optional(return_type);
	Type *result_type = type_add_optional(subscript_expr->type, is_optional_result);
	expr_insert_addr(increased);
	Expr *index = exprptr(subscript_expr->subscript_assign_expr.index);
	Decl *temp_val = decl_new_generated_var(increased->type, VARDECL_LOCAL, increased->span);
	Decl *index_val = decl_new_generated_var(index->type, VARDECL_LOCAL, index->span);
	Decl *value_val = decl_new_generated_var(return_type, VARDECL_LOCAL, main->span);
	Decl *result_val = decl_new_generated_var(result_type, VARDECL_LOCAL, main->span);
	Expr *decl_expr = expr_generate_decl(temp_val, increased);
	Expr *decl_index_expr = expr_generate_decl(index_val, index);
	Expr *mutate = expr_copy(main);
	mutate->resolve_status = RESOLVE_NOT_DONE;
	mutate->type = NULL;
	switch (main->expr_kind)
	{
		case EXPR_UNARY:
		case EXPR_POST_UNARY:
			mutate->unary_expr.expr = expr_variable(value_val);
			break;
		case EXPR_BINARY:
			mutate->binary_expr.left = exprid(expr_variable(value_val));
			break;
		default:
			UNREACHABLE
	}
	main->expr_kind = EXPR_EXPRESSION_LIST;
	main->expression_list = NULL;
	// temp = indexed
	vec_add(main->expression_list, decl_expr);
	// temp_index = index
	vec_add(main->expression_list, decl_index_expr);
	Expr *get_expr = expr_new(EXPR_ACCESS_RESOLVED, increased->span);
	vec_add(args, expr_variable(index_val));
	Expr *temp_val_1 = expr_variable(temp_val);
	if (!sema_expr_rewrite_insert_deref(context, temp_val_1)) return false;
	if (!sema_insert_method_call(context, get_expr, operator, temp_val_1, args, false)) return false;
	Expr *value_val_expr = expr_generate_decl(value_val, get_expr);
	// temp_value = func(temp, temp_index)
	vec_add(main->expression_list, value_val_expr);
	// temp_result = temp_value++, temp_result *= temp_value etc
	vec_add(main->expression_list, expr_generate_decl(result_val, mutate));

	args = NULL;
	vec_add(args, expr_variable(index_val));
	vec_add(args, expr_variable(value_val));
	Expr *temp_val_2 = expr_variable(temp_val);
	if (!sema_expr_rewrite_insert_deref(context, temp_val_2)) return false;
	if (!sema_insert_method_call(context, subscript_expr, declptr(subscript_expr->subscript_assign_expr.method), temp_val_2, args, false)) return false;
	ASSERT(subscript_expr->expr_kind == EXPR_CALL);
	subscript_expr->call_expr.has_optional_arg = false;
	vec_add(main->expression_list, subscript_expr);
	vec_add(main->expression_list, expr_variable(result_val));
	return sema_expr_analyse_expr_list(context, main);

}
/**
 * Analyse foo++ foo-- --foo ++foo
 * @return false if analysis fails.
 */
static inline bool sema_expr_analyse_incdec(SemaContext *context, Expr *expr)
{
	// 1. Analyse the lvalue to update
	Expr *inner = expr->unary_expr.expr;
	if (!sema_analyse_expr_lvalue(context, inner, NULL)) return false;

	// 2. Assert it's an l-value
	if (!sema_expr_check_assign(context, inner, NULL)) return false;

	// 3. This might be a $foo, if to handle it.
	if (inner->expr_kind == EXPR_CT_IDENT)
	{
		return sema_expr_analyse_ct_incdec(context, expr, inner);
	}

	if (inner->expr_kind == EXPR_CT_SUBSCRIPT)
	{
		return sema_expr_analyse_ct_subscript_incdec(context, expr, inner);
	}

	// 4. Flatten typedef, enum, distinct, optional
	Type *type = type_flatten(inner->type);

	// 5. We can only inc/dec numbers or pointers.
	if (!type_underlying_may_add_sub(type) && type->type_kind != TYPE_VECTOR)
	{
		RETURN_SEMA_ERROR(inner, "The expression must be a vector, enum, number or a pointer.");
	}

	if (inner->expr_kind == EXPR_SUBSCRIPT_ASSIGN)
	{
		return sema_analyse_assign_mutate_overloaded_subscript(context, expr, inner, type);
	}
	// 6. Done, the result is same as the inner type.
	expr->type = inner->type;
	return true;
}

/**
 * Take an address of a temporary &&x.
 */
static inline bool sema_expr_analyse_taddr(SemaContext *context, Expr *expr, bool *failed_ref)
{
	Expr *inner = expr->unary_expr.expr;
	if (!sema_analyse_expr_rvalue(context, inner)) return false;

	Type *type = inner->type;

	switch (sema_resolve_storage_type(context, type))
	{
		case STORAGE_ERROR:
			return false;
		case STORAGE_NORMAL:
			break;
		default:
			if (failed_ref)
			{
				*failed_ref = true;
				return false;
			}
			RETURN_SEMA_ERROR(expr, "It is not possible to take the address from a value of type %s.",
							  type_quoted_error_string(type));
	}
	// 2. The type is the resulting type of the expression.
	expr->type = type_get_ptr_recurse(inner->type);
	return true;
}

INLINE bool expr_is_ungrouped_binary(Expr *expr)
{
	return expr->expr_kind == EXPR_BINARY && !expr->binary_expr.grouped;
}

static bool sema_binary_check_unclear_op_precedence(Expr *left_side, Expr * main_expr, Expr *right_side)
{
	static int BINOP_PREC_REQ[BINARYOP_LAST + 1] = {
			// bitwise operations
			[BINARYOP_BIT_OR] = 1,
			[BINARYOP_BIT_XOR] = 1,
			[BINARYOP_BIT_AND] = 1,

			// comparison operations
			[BINARYOP_GT] = 2,
			[BINARYOP_GE] = 2,
			[BINARYOP_LT] = 2,
			[BINARYOP_LE] = 2,
			[BINARYOP_NE] = 2,
			[BINARYOP_EQ] = 2,

			[BINARYOP_SHR] = 3,
			[BINARYOP_SHL] = 3,
	};

	BinaryOp main_op = main_expr->binary_expr.operator;
	int precedence_main = BINOP_PREC_REQ[main_op];
	if (expr_is_ungrouped_binary(left_side))
	{
		int left_op = left_side->binary_expr.operator;
		int precedence_left = BINOP_PREC_REQ[left_op];
		if (precedence_left && (precedence_left == precedence_main))
		{
			// ^|& has special rules
			if (precedence_left != 1 || left_op != main_op) return true;
		}
	}
	if (expr_is_ungrouped_binary(right_side))
	{
		int right_op = right_side->binary_expr.operator;
		int precedence_right = BINOP_PREC_REQ[right_op];
		if (precedence_right && (precedence_right == precedence_main))
		{
			// ^|& has special rules
			if (precedence_right != 1 || right_op != main_op) return true;
		}
	}
	return false;
}


INLINE bool expr_is_ungrouped_ternary(Expr *expr)
{
	return expr->expr_kind == EXPR_TERNARY && !expr->ternary_expr.grouped;
}

static inline bool sema_expr_analyse_or_error(SemaContext *context, Expr *expr, Expr *left, Expr *right, Type *infer_type, bool *failed_ref)
{
	bool lhs_is_embed = left->expr_kind == EXPR_EMBED;
	if (expr_is_ungrouped_ternary(left) || expr_is_ungrouped_ternary(right))
	{
		RETURN_SEMA_ERROR(expr, "Unclear precedence using ternary with ??, please use () to remove ambiguity.");
	}
	if (lhs_is_embed)
	{
		if (!sema_expr_analyse_embed(context, left, true)) return false;
	}
	else
	{
		if (!sema_analyse_inferred_expr(context, infer_type, left, NULL)) return false;
	}

	Type *type = left->type;
	if (!type_is_optional(type))
	{
		if (lhs_is_embed)
		{
			expr_replace(expr, left);
			return true;
		}
		CHECK_ON_DEFINED(failed_ref);
		RETURN_SEMA_ERROR(left, "No optional to use '\?\?' with, please remove the '\?\?'.");
	}

	EndJump active_scope_jump = context->active_scope.end_jump;

	// First we analyse the "else" and try to implicitly cast.
	if (!sema_analyse_inferred_expr(context, infer_type, right, NULL)) return false;

	if (left->expr_kind == EXPR_OPTIONAL)
	{
		expr_replace(expr, right);
		return true;
	}

	// Ignore the jump here.
	context->active_scope.end_jump = active_scope_jump;

	// Here we might need to insert casts.
	Type *else_type = right->type;


	// Remove any possible optional of the else type.
	bool add_optional = type_is_optional(else_type);
	type = type_no_optional(type);
	else_type = type_no_optional(else_type);
	Type *common = type_find_max_type(type, else_type, left, right);
	if (!common)
	{
		CHECK_ON_DEFINED(failed_ref);
		if (else_type == type_fault)
		{
			RETURN_SEMA_ERROR(right, "There is no common type for %s and %s, did you perhaps forget a '?' after the last expression?", type_quoted_error_string(type), type_quoted_error_string(else_type));
		}
		RETURN_SEMA_ERROR(right, "Cannot find a common type for %s and %s.", type_quoted_error_string(type), type_quoted_error_string(else_type));
	}
	if (!cast_both_implicit(context, left, right, common, false, failed_ref)) return false;
	expr->type = type_add_optional(common, add_optional);
	return true;
}

static inline bool sema_expr_analyse_ct_and_or(SemaContext *context, Expr *expr, Expr *left, Expr *right)
{
	ASSERT_SPAN(expr, expr->resolve_status == RESOLVE_RUNNING);
	bool is_and = expr->binary_expr.operator == BINARYOP_CT_AND;
	if (!sema_analyse_expr_rvalue(context, left)) return false;
	if (!sema_cast_const(left) || !expr_is_const_bool(left)) RETURN_SEMA_ERROR(left, "Expected this to evaluate to a constant boolean.");
	if (left->const_expr.b != is_and)
	{
		expr_rewrite_const_bool(expr, type_bool, !is_and);
		return true;
	}
	if (!sema_analyse_expr_rvalue(context, right)) return false;
	if (sema_cast_const(right) && expr_is_const_bool(right))
	{
		expr_rewrite_const_bool(expr, type_bool, right->const_expr.b);
		return true;
	}
	if (!cast_implicit(context, right, type_bool, false)) return false;
	expr_replace(expr, right);
	return true;
}

static bool sema_expr_analyse_ct_concat_assign(SemaContext *context, Expr *concat_expr, Expr *left, Expr *right, bool *failed_ref)
{
	ASSERT_SPAN(concat_expr, concat_expr->resolve_status == RESOLVE_RUNNING);
	Expr *left_copy = copy_expr_single(left);
	if (!sema_analyse_expr_lvalue(context, left_copy, NULL)) return false;
	if (!sema_analyse_expr_rvalue(context, left)) return false;
	if (!sema_analyse_inferred_expr(context, left->type, right, NULL)) return false;
	if (!sema_expr_analyse_ct_concat(context, concat_expr, left, right, NULL)) return false;
	Expr *result_copy = copy_expr_single(concat_expr);
	concat_expr->binary_expr.left = exprid(left_copy);
	concat_expr->binary_expr.right = exprid(result_copy);
	concat_expr->resolve_status = RESOLVE_NOT_DONE;
	concat_expr->binary_expr.operator = BINARYOP_ASSIGN;
	concat_expr->expr_kind = EXPR_BINARY;
	concat_expr->resolve_status = RESOLVE_RUNNING;
	return sema_expr_analyse_binary(context, NULL, concat_expr, failed_ref);
}

static inline bool sema_expr_analyse_binary(SemaContext *context, Type *infer_type, Expr *expr, bool *failed_ref)
{
	ASSERT_SPAN(expr, expr->resolve_status == RESOLVE_RUNNING);
	Expr *left = exprptr(expr->binary_expr.left);
	Expr *right = exprptr(expr->binary_expr.right);
	// check if both sides have a binary operation where the precedence is unclear. Example: a ^ b | c
	if (sema_binary_check_unclear_op_precedence(left, expr, right))
	{
		RETURN_SEMA_ERROR(expr, "You need to add explicit parentheses to clarify precedence.");
	}
	BinaryOp operator = expr->binary_expr.operator;
	if (operator == BINARYOP_CT_CONCAT_ASSIGN)
	{
		return sema_expr_analyse_ct_concat_assign(context, expr, left, right, failed_ref);
	}
	if (operator >= BINARYOP_ASSIGN)
	{
		if (left->expr_kind != EXPR_TYPEINFO)
		{
			if (!sema_analyse_expr_lvalue(context, left, failed_ref)) return false;
		}
	}
	else
	{
		if (operator == BINARYOP_ELSE) return sema_expr_analyse_or_error(context, expr, left, right, infer_type, failed_ref);
		if (!sema_binary_analyse_with_inference(context, left, right, operator)) return false;
	}
	switch (operator)
	{
		case BINARYOP_ELSE:
			UNREACHABLE
		case BINARYOP_CT_CONCAT:
			return sema_expr_analyse_ct_concat(context, expr, left, right, failed_ref);
		case BINARYOP_CT_CONCAT_ASSIGN:
			UNREACHABLE
		case BINARYOP_CT_OR:
		case BINARYOP_CT_AND:
			return sema_expr_analyse_ct_and_or(context, expr, left, right);
		case BINARYOP_ASSIGN:
			return sema_expr_analyse_assign(context, expr, left, right, failed_ref);
		case BINARYOP_MULT:
			return sema_expr_analyse_mult(context, expr, left, right, failed_ref);
		case BINARYOP_ADD:
			return sema_expr_analyse_add(context, expr, left, right, failed_ref);
		case BINARYOP_SUB:
			return sema_expr_analyse_sub(context, expr, left, right, failed_ref);
		case BINARYOP_DIV:
			return sema_expr_analyse_div(context, expr, left, right, failed_ref);
		case BINARYOP_MOD:
			return sema_expr_analyse_mod(context, expr, left, right, failed_ref);
		case BINARYOP_AND:
		case BINARYOP_OR:
			return sema_expr_analyse_and_or(context, expr, left, right, failed_ref);
		case BINARYOP_BIT_OR:
			return sema_expr_analyse_bit(context, expr, left, right, OVERLOAD_OR, failed_ref);
		case BINARYOP_BIT_XOR:
			return sema_expr_analyse_bit(context, expr, left, right, OVERLOAD_XOR, failed_ref);
		case BINARYOP_BIT_AND:
			return sema_expr_analyse_bit(context, expr, left, right, OVERLOAD_AND, failed_ref);
		case BINARYOP_VEC_NE:
		case BINARYOP_VEC_EQ:
		case BINARYOP_VEC_GT:
		case BINARYOP_VEC_GE:
		case BINARYOP_VEC_LT:
		case BINARYOP_VEC_LE:
			UNREACHABLE
		case BINARYOP_NE:
		case BINARYOP_EQ:
		case BINARYOP_GT:
		case BINARYOP_GE:
		case BINARYOP_LT:
		case BINARYOP_LE:
			return sema_expr_analyse_comp(context, expr, left, right, failed_ref);
		case BINARYOP_SHR:
		case BINARYOP_SHL:
			return sema_expr_analyse_shift(context, expr, left, right, failed_ref);
		case BINARYOP_ADD_ASSIGN:
		case BINARYOP_SUB_ASSIGN:
		case BINARYOP_BIT_AND_ASSIGN:
		case BINARYOP_BIT_OR_ASSIGN:
		case BINARYOP_BIT_XOR_ASSIGN:
		case BINARYOP_MOD_ASSIGN:
		case BINARYOP_MULT_ASSIGN:
		case BINARYOP_DIV_ASSIGN:
		case BINARYOP_SHR_ASSIGN:
		case BINARYOP_SHL_ASSIGN:
			return sema_expr_analyse_op_assign(context, expr, left, right, operator);
		case BINARYOP_ERROR:
			break;
	}
	UNREACHABLE
}

/**
 * Analyse:
 *   *x
 *   &x
 *   x++/++x/x--/--x
 *   ~x
 *   !x
 *   &&x
 *   -x
 */
static inline bool sema_expr_analyse_unary(SemaContext *context, Expr *expr, bool *failed_ref)
{
	if (failed_ref) *failed_ref = false;
	ASSERT_SPAN(expr, expr->resolve_status == RESOLVE_RUNNING);
	switch (expr->unary_expr.operator)
	{
		case UNARYOP_DEREF:
			return sema_expr_analyse_deref(context, expr, failed_ref);
		case UNARYOP_ADDR:
			return sema_expr_analyse_addr(context, expr, failed_ref);
		case UNARYOP_NEG:
		case UNARYOP_PLUS:
			return sema_expr_analyse_neg_plus(context, expr);
		case UNARYOP_BITNEG:
			return sema_expr_analyse_bit_not(context, expr, failed_ref);
		case UNARYOP_NOT:
			return sema_expr_analyse_not(context, expr);
		case UNARYOP_DEC:
		case UNARYOP_INC:
			return sema_expr_analyse_incdec(context, expr);
		case UNARYOP_TADDR:
			return sema_expr_analyse_taddr(context, expr, failed_ref);
		case UNARYOP_ERROR:
			return false;
	}
	UNREACHABLE
}


static inline bool sema_expr_analyse_rethrow(SemaContext *context, Expr *expr, Type *to)
{
	if (context->call_env.kind != CALL_ENV_FUNCTION)
	{
		if (CALL_ENV_FUNCTION_STATIC)
		{
			RETURN_SEMA_ERROR(expr, "Rethrow cannot be used for a static initializer.");
		}
		RETURN_SEMA_ERROR(expr, "Rethrow cannot be used outside of a function.");
	}

	Expr *inner = expr->rethrow_expr.inner;
	if (!sema_analyse_expr_rvalue(context, inner)) return false;

	if (context->active_scope.in_defer) RETURN_SEMA_ERROR(expr, "Rethrows are not allowed inside of defers.");

	expr->type = type_no_optional(inner->type);

	if (!IS_OPTIONAL(inner))
	{
		RETURN_SEMA_ERROR(expr, "No optional to rethrow before '!' in the expression, please remove '!'.");
	}

	if (context->active_scope.flags & SCOPE_MACRO)
	{
		TypeInfoId rtype = context->current_macro->func_decl.signature.rtype;
		if (rtype && !type_is_optional(typeget(rtype)))
		{
			RETURN_SEMA_ERROR(expr, "Rethrow is only allowed in macros with an optional or inferred return type. "
									"Did you mean to use '!!' instead?");
		}
		vec_add(context->block_returns, NULL);
		expr->rethrow_expr.in_block = context->block_exit_ref;
		expr->rethrow_expr.cleanup = context_get_defers(context, context->block_return_defer, false);
	}
	else
	{
		expr->rethrow_expr.cleanup = context_get_defers(context, 0, false);
		expr->rethrow_expr.in_block = NULL;
		if (context->rtype && context->rtype->type_kind != TYPE_OPTIONAL)
		{
			// Sometimes people write "int? foo = bar()!;" which is likely a mistake.
			if (to && type_is_optional(to))
			{
				RETURN_SEMA_ERROR(expr, "This expression is doing a rethrow, "
										"but '%s' returns %s, which isn't an optional type. Since you are assigning to "
				                        "an optional, maybe you added '!' by mistake?",
										context->call_env.current_function->name,
										type_quoted_error_string(context->rtype));
			}
			RETURN_SEMA_ERROR(expr, "This expression is doing a rethrow, "
									"but '%s' returns %s, which isn't an optional type. Did you intend to use '!!' instead?",
									context->call_env.current_function->name,
									type_quoted_error_string(context->rtype));
		}
		if (context->call_env.is_naked_fn)
		{
			RETURN_SEMA_ERROR(expr, "Rethrow is not allowed in a '@naked' function.");
		}
	}
	return true;
}


// Analyse x!!
static inline bool sema_expr_analyse_force_unwrap(SemaContext *context, Expr *expr)
{
	Expr *inner = expr->inner_expr;
	if (!sema_analyse_expr_rvalue(context, inner)) return false;
	if (!IS_OPTIONAL(inner))
	{
		RETURN_SEMA_ERROR(expr, "No optional to rethrow before '!!' in the expression, please remove '!!'.");
	}
	expr->type = type_no_optional(inner->type);
	return true;
}

static inline bool sema_expr_analyse_typeid(SemaContext *context, Expr *expr)
{
	if (!sema_resolve_type_info(context, expr->typeid_expr, RESOLVE_TYPE_DEFAULT)) return expr_poison(expr);
	Type *type = expr->type_expr->type;
	expr->expr_kind = EXPR_CONST;
	expr->resolve_status = RESOLVE_DONE;
	expr->const_expr.const_kind = CONST_TYPEID;
	expr->const_expr.typeid = type->canonical;
	expr->type = type_typeid;
	return true;
}

static inline bool sema_expr_analyse_optional(SemaContext *context, Expr *expr, bool *failed_ref)
{
	Expr *inner = expr->inner_expr;
	if (failed_ref) *failed_ref = false;
	if (!sema_analyse_expr_rvalue(context, inner)) return false;

	if (IS_OPTIONAL(inner))
	{
		if (failed_ref) goto ON_FAILED;
		RETURN_SEMA_ERROR(inner, "The inner expression is already an optional.");
	}

	if (inner->expr_kind == EXPR_OPTIONAL)
	{
		if (failed_ref) goto ON_FAILED;
		RETURN_SEMA_ERROR(inner, "It looks like you added one too many '?' after the error.");
	}

	Type *type = inner->type->canonical;

	if (type != type_fault)
	{
		if (failed_ref) goto ON_FAILED;
		RETURN_SEMA_ERROR(inner, "You cannot use the '?' operator on expressions of type %s",
						  type_quoted_error_string(type));
	}
	ASSERT_SPAN(expr, type->type_kind == TYPE_ANYFAULT || type->decl->resolve_status == RESOLVE_DONE);
	expr->type = type_wildcard_optional;
	return true;
ON_FAILED:
	*failed_ref = true;
	return false;
}

static inline bool sema_expr_analyse_compiler_const(SemaContext *context, Expr *expr, bool report_missing)
{
	const char *string = expr->builtin_expr.ident;
	BuiltinDefine def = BUILTIN_DEF_NONE;
	for (unsigned i = 0; i < NUMBER_OF_BUILTIN_DEFINES; i++)
	{
		if (string == builtin_defines[i])
		{
			def = (BuiltinDefine)i;
			break;
		}
	}
	switch (def)
	{
		case BUILTIN_DEF_TIME:
			expr_rewrite_const_string(expr, time_get());
			return true;
		case BUILTIN_DEF_DATE:
			expr_rewrite_const_string(expr, date_get());
			return true;
		case BUILTIN_DEF_FILE:
			if (context->call_env.current_function)
			{
				expr_rewrite_const_string(expr, context->call_env.current_function->unit->file->name);
				return true;
			}
			expr_rewrite_const_string(expr, context->compilation_unit->file->name);
			return true;
		case BUILTIN_DEF_FILEPATH:
			if (context->call_env.current_function)
			{
				expr_rewrite_const_string(expr, context->call_env.current_function->unit->file->full_path);
				return true;
			}
			expr_rewrite_const_string(expr, context->compilation_unit->file->full_path);
			return true;
		case BUILTIN_DEF_MODULE:
			if (context->original_module)
			{
				expr_rewrite_const_string(expr, context->original_module->name->module);
			}
			else
			{
				expr_rewrite_const_string(expr, context->compilation_unit->module->name->module);
			}
			return true;
		case BUILTIN_DEF_LINE:
			if (context->original_inline_line)
			{
				expr_rewrite_const_int(expr, type_isz, context->original_inline_line);
			}
			else
			{
				expr_rewrite_const_int(expr, type_isz, expr->span.row);
			}
			return true;
		case BUILTIN_DEF_LINE_RAW:
			expr_rewrite_const_int(expr, type_isz, expr->span.row);
			return true;
		case BUILTIN_DEF_FUNCTION:
			switch (context->call_env.kind)
			{
				case CALL_ENV_GLOBAL_INIT:
				case CALL_ENV_ATTR:
					if (report_missing)
					{
						SEMA_ERROR(expr, "$$FUNCEXPR is not defined outside of a function.");
					}
					return false;
				case CALL_ENV_FUNCTION:
				case CALL_ENV_FUNCTION_STATIC:
					expr->expr_kind = EXPR_IDENTIFIER;
					expr_resolve_ident(expr, context->call_env.current_function);
					return true;
			}
			UNREACHABLE
		case BUILTIN_DEF_FUNC:
			switch (context->call_env.kind)
			{
				case CALL_ENV_GLOBAL_INIT:
					expr_rewrite_const_string(expr, "<GLOBAL>");
					return true;
				case CALL_ENV_FUNCTION_STATIC:
				case CALL_ENV_FUNCTION:
				{
					Decl *current_func = context->call_env.current_function;
					TypeInfo *func_type = type_infoptrzero(current_func->func_decl.type_parent);
					if (func_type)
					{
						scratch_buffer_clear();
						scratch_buffer_append(func_type->type->name);
						scratch_buffer_append_char('.');
						scratch_buffer_append(current_func->name);
						expr_rewrite_const_string(expr, scratch_buffer_copy());
						return true;
					}
					expr_rewrite_const_string(expr, current_func->name);
					return true;
				}
				case CALL_ENV_ATTR:
					expr_rewrite_const_string(expr, "<attribute>");
					return true;
			}
			UNREACHABLE
		case BUILTIN_DEF_NONE:
		{
			Expr *value = htable_get(&compiler.context.compiler_defines, (void *)string);
			if (!value)
			{
				if (report_missing)
				{
					SEMA_ERROR(expr, "The compiler constant '%s' was not defined, did you mistype or forget to add it?", string);
				}
				return false;
			}
			expr_replace(expr, value);
			return true;
		}

		case BUILTIN_DEF_BENCHMARK_NAMES:
			if (!compiler.build.benchmarking)
			{
				expr_rewrite_const_empty_slice(expr, type_get_slice(type_string));
				return true;
			}
			expr->type = type_get_slice(type_string);
			expr->benchmark_hook_expr = BUILTIN_DEF_BENCHMARK_NAMES;
			expr->expr_kind = EXPR_BENCHMARK_HOOK;
			return true;
		case BUILTIN_DEF_BENCHMARK_FNS:
			if (!compiler.build.benchmarking)
			{
				expr_rewrite_const_empty_slice(expr, type_get_slice(type_voidptr));
				return true;
			}
			expr->type = type_get_slice(type_voidptr);
			expr->benchmark_hook_expr = BUILTIN_DEF_BENCHMARK_FNS;
			expr->expr_kind = EXPR_BENCHMARK_HOOK;
			return true;
		case BUILTIN_DEF_TEST_NAMES:
			if (!compiler.build.testing)
			{
				expr_rewrite_const_empty_slice(expr, type_get_slice(type_string));
				return true;
			}
			expr->type = type_get_slice(type_string);
			expr->test_hook_expr = BUILTIN_DEF_TEST_NAMES;
			expr->expr_kind = EXPR_TEST_HOOK;
			return true;
		case BUILTIN_DEF_TEST_FNS:
			if (!compiler.build.testing)
			{
				expr_rewrite_const_empty_slice(expr, type_get_slice(type_voidptr));
				return true;
			}
			expr->type = type_get_slice(type_voidptr);
			expr->test_hook_expr = BUILTIN_DEF_TEST_FNS;
			expr->expr_kind = EXPR_TEST_HOOK;
			return true;
	}
	UNREACHABLE
}



static Decl *sema_expr_analyse_var_path(SemaContext *context, Expr *expr, bool *failed_ref)
{
	if (!sema_analyse_expr(context, expr)) return NULL;
	Expr *current = expr;
	Decl *decl = NULL;
RETRY:
	switch (current->expr_kind)
	{
		case EXPR_CT_IDENT:
			current = current->ident_expr->var.init_expr;
			goto RETRY;
		case EXPR_IDENTIFIER:
			decl = current->ident_expr;
			break;
		default:
			if (failed_ref)
			{
				*failed_ref = true;
				return NULL;
			}
			SEMA_ERROR(expr, "A variable was expected here.");

			return NULL;
	}
	if (!sema_analyse_decl(context, decl)) return NULL;
	return decl;
}

static inline bool sema_expr_analyse_decl_element(SemaContext *context, DesignatorElement *element, Type *type, Decl **member_ref, ArraySize *index_ref, Type **return_type, unsigned i, SourceSpan loc,
												  bool *is_missing)
{
	DesignatorType kind = element->kind;
	if (kind == DESIGNATOR_RANGE) RETURN_SEMA_ERROR(element->index_expr, "Ranges are not allowed.");
	Type *actual_type = type_flatten(type);
	if (kind == DESIGNATOR_ARRAY)
	{
		Expr *inner = element->index_expr;
		if (!type_is_arraylike(actual_type) && actual_type->type_kind != TYPE_POINTER)
		{
			if (is_missing)
			{
				*is_missing = true;
				return false;
			}
			RETURN_SEMA_ERROR(inner, "It's not possible to constant index into something that is not an array nor vector.");
		}
		if (!sema_analyse_expr_rvalue(context, inner)) return false;
		if (!type_is_integer(inner->type))
		{
			RETURN_SEMA_ERROR(inner, "Expected an integer index.");
		}
		if (!sema_cast_const(inner))
		{
			RETURN_SEMA_ERROR(inner, "Expected a constant index.");
		}
		Int value = inner->const_expr.ixx;
		if (!int_fits(value, type_isz->canonical->type_kind))
		{
			RETURN_SEMA_ERROR(inner, "The index is out of range for a %s.", type_quoted_error_string(type_isz));
		}
		if (int_is_neg(value)) RETURN_SEMA_ERROR(inner, "The index must be zero or greater.");

		type = actual_type->array.base;
		ArraySize len = actual_type->array.len;
		int64_t index = int_to_i64(value);
		if (len && index >= len)
		{
			if (is_missing)
			{
				*is_missing = true;
				*index_ref = 0;
				return false;
			}
			RETURN_SEMA_ERROR(inner, "Index exceeds array bounds.");
		}
		*return_type = type;
		*index_ref = index;
		*member_ref = NULL;
		return true;
	}
	Expr *field = sema_expr_resolve_access_child(context, element->field_expr, is_missing);
	if (!field) return false;
	if (field->expr_kind != EXPR_UNRESOLVED_IDENTIFIER) RETURN_SEMA_ERROR(field, "Expected an identifier here.");
	const char *kw = field->unresolved_ident_expr.ident;
	if (kw == kw_ptr)
	{
		switch (actual_type->type_kind)
		{
			case TYPE_SLICE:
				*member_ref = NULL;
				*return_type = actual_type->array.base;
				return true;
			case TYPE_INTERFACE:
			case TYPE_ANY:
				*member_ref = NULL;
				*return_type = type_voidptr;
				return true;
			default:
				break;
		}
	}
	if (kw == kw_len)
	{
		if (type_is_arraylike(actual_type) || actual_type->type_kind == TYPE_SLICE)
		{
			*member_ref = NULL;
			*return_type = type_usz;
			return true;
		}
	}
	if (actual_type->type_kind == TYPE_POINTER && actual_type->pointer->type_kind != TYPE_POINTER)
	{
		actual_type = actual_type->pointer;
	}
	if (!type_is_union_or_strukt(actual_type))
	{
		if (is_missing)
		{
			*is_missing = true;
			return false;
		}
		if (i == 0)
		{
			sema_error_at(context, loc, "%s has no members.", type_quoted_error_string(type));
		}
		else
		{
			sema_error_at(context, loc, "There is no such member in %s.", type_quoted_error_string(type));
		}
		return false;
	}
	Decl *member = sema_decl_stack_find_decl_member(context, actual_type->decl, kw, METHODS_AND_FIELDS);
	if (!decl_ok(member)) return false;
	if (!member)
	{
		if (is_missing)
		{
			*is_missing = true;
			return false;
		}
		sema_error_at(context, loc, "There is no such member in %s.", type_quoted_error_string(type));
		return false;
	}
	*member_ref = member;
	*return_type = member->type;
	return true;
}

static inline bool sema_expr_analyse_ct_alignof(SemaContext *context, Expr *expr, bool *failed_ref)
{
	Expr *main_var = expr->ct_call_expr.main_var;
	DesignatorElement **path = expr->ct_call_expr.flat_path;
	Decl *decl = sema_expr_analyse_var_path(context, main_var, failed_ref);
	if (!decl) return false;
	Type *type = decl->type;
	switch (sema_resolve_storage_type(context, type))
	{
		case STORAGE_ERROR:
			return false;
		case STORAGE_NORMAL:
			break;
		default:
			if (failed_ref)
			{
				*failed_ref = true;
				return false;
			}
			RETURN_SEMA_ERROR(main_var, "Cannot use '$alignof' on type %s.", type_quoted_error_string(type));
	}
	AlignSize align;
	if (decl && !decl_is_user_defined_type(decl))
	{
		align = decl->alignment;
	}
	else
	{
		if (!sema_set_abi_alignment(context, type, &align)) return false;
	}
	FOREACH_IDX(i, DesignatorElement *, element, path)
	{
		Decl *member;
		ArraySize index = 0;
		Type *result_type;
		if (!sema_expr_analyse_decl_element(context,
											element,
											type,
											&member,
											&index,
											&result_type,
											i,
											i == 0 ? main_var->span : expr->span,
											NULL)) return false;
		if (member)
		{
			align = type_min_alignment(member->offset, align);
		}
		else
		{
			TypeSize size = type_size(result_type);
			align = type_min_alignment(size * index, align);
		}
		type = result_type;
	}
	expr_rewrite_const_int(expr, type_isz, align);
	return true;
}

static inline void sema_expr_rewrite_to_type_nameof(Expr *expr, Type *type, TokenType name_type)
{
	if (type_is_func_ptr(type)) type = type->pointer->function.prototype->raw_type;
	if (name_type == TOKEN_CT_EXTNAMEOF)
	{
		if (type_is_user_defined(type))
		{
			scratch_buffer_set_extern_decl_name(type->decl, true);
			expr_rewrite_const_string(expr, scratch_buffer_copy());
		}
		else
		{
			expr_rewrite_const_string(expr, type->name);
		}
		return;
	}

	if (name_type == TOKEN_CT_NAMEOF || type_is_builtin(type->type_kind))
	{
		expr_rewrite_const_string(expr, type->name);
		return;
	}
	scratch_buffer_clear();

	Module *module = type_base_module(type);
	if (module)
	{
		scratch_buffer_append(module->name->module);
		scratch_buffer_append("::");
	}
	scratch_buffer_append(type->name);
	expr_rewrite_const_string(expr, scratch_buffer_copy());
}

static inline bool sema_expr_analyse_ct_nameof(SemaContext *context, Expr *expr, bool *failed_ref)
{
	Expr *main_var = expr->ct_call_expr.main_var;
	Decl *decl = sema_expr_analyse_var_path(context, main_var, failed_ref);
	if (!decl) return false;

	TokenType name_type = expr->ct_call_expr.token_type;

	if (vec_size(expr->ct_call_expr.flat_path))
	{
		SEMA_ERROR(main_var, "You can only take the name of types and variables, not their sub elements.");
		return false;
	}

	if (name_type == TOKEN_CT_EXTNAMEOF)
	{
		switch (decl->decl_kind)
		{
			case DECL_VAR:
				switch (decl->var.kind)
				{
					case VARDECL_CONST:
					case VARDECL_GLOBAL:
						goto RETURN_CT;
					case VARDECL_LOCAL:
					case VARDECL_PARAM:
					case VARDECL_MEMBER:
					case VARDECL_BITMEMBER:
					case VARDECL_PARAM_EXPR:
					case VARDECL_UNWRAPPED:
					case VARDECL_ERASE:
					case VARDECL_REWRAPPED:
					case VARDECL_PARAM_CT:
					case VARDECL_PARAM_CT_TYPE:
					case VARDECL_LOCAL_CT:
					case VARDECL_LOCAL_CT_TYPE:
						// TODO verify that all of these are correct.
						break;
				}
				FALLTHROUGH;
			case DECL_ALIAS:
			case DECL_ALIAS_PATH:
			case DECL_ATTRIBUTE:
			case DECL_BODYPARAM:
			case DECL_CT_ASSERT:
			case DECL_CT_ECHO:
			case DECL_CT_EXEC:
			case DECL_CT_INCLUDE:
			case DECL_DECLARRAY:
			case DECL_ERASED:
			case DECL_GROUP:
			case DECL_IMPORT:
			case DECL_LABEL:
			case DECL_MACRO:
			case DECL_POISONED:
				RETURN_SEMA_ERROR(main_var, "'%s' does not have an external name.", decl->name);
			case DECL_FAULT:
				goto RETURN_CT;
			case DECL_BITSTRUCT:
			case DECL_TYPEDEF:
			case DECL_ENUM:
			case DECL_CONST_ENUM:
			case DECL_ENUM_CONSTANT:
			case DECL_FNTYPE:
			case DECL_FUNC:
			case DECL_INTERFACE:
			case DECL_STRUCT:
			case DECL_TYPE_ALIAS:
			case DECL_UNION:
				// TODO verify that all of these are correct
				goto RETURN_CT; // NOLINT
		}
RETURN_CT:
		scratch_buffer_set_extern_decl_name(decl, true);
		expr_rewrite_const_string(expr, scratch_buffer_to_string());
		return true;
	}
	if (!decl->unit || name_type == TOKEN_CT_NAMEOF || decl_is_var_local(decl))
	{
		expr_rewrite_const_string(expr, decl->name);
		return true;
	}
	scratch_buffer_clear();
	scratch_buffer_append(decl->unit->module->name->module);
	scratch_buffer_append("::");
	scratch_buffer_append(decl->name);
	expr_rewrite_const_string(expr, scratch_buffer_copy());
	return true;
}


static Type *sema_expr_check_type_exists(SemaContext *context, TypeInfo *type_info)
{
	if (type_info->resolve_status == RESOLVE_DONE)
	{
		return type_info->type;
	}
RETRY:
	switch (type_info->kind)
	{
		case TYPE_INFO_POISON:
			return poisoned_type;
		case TYPE_INFO_GENERIC:
		{
			TypeInfo *base = type_info->generic.base;
			if (base->kind == TYPE_INFO_IDENTIFIER)
			{
				if (!sema_parameterized_type_is_found(context, base->unresolved.path, base->unresolved.name, type_info->span)) return NULL;
			}
			if (!sema_resolve_type_info(context, type_info, RESOLVE_TYPE_DEFAULT)) return poisoned_type;
			return type_info->type;
		}
		case TYPE_INFO_VECTOR:
		{
			ArraySize size;
			if (!sema_resolve_array_like_len(context, type_info, &size)) return poisoned_type;
			Type *type = sema_expr_check_type_exists(context, type_info->array.base);
			if (!type) return NULL;
			if (!type_ok(type)) return type;
			if (!type_is_valid_for_vector(type))
			{
				SEMA_ERROR(type_info->array.base,
						   "%s is not of a vectorizable type.",
						   type_quoted_error_string(type));
				return poisoned_type;
			}
			return type_get_vector(type, size);
		}
		case TYPE_INFO_ARRAY:
		{
			ArraySize size;
			if (!sema_resolve_array_like_len(context, type_info, &size)) return poisoned_type;
			Type *type = sema_expr_check_type_exists(context, type_info->array.base);
			if (!type) return NULL;
			if (!type_ok(type)) return type;
			if (!type_is_valid_for_array(type))
			{
				SEMA_ERROR(type_info->array.base,
						   "You cannot form an array with elements of type %s.",
						   type_quoted_error_string(type));
				return poisoned_type;
			}
			return type_get_array(type, size);
		}
		case TYPE_INFO_CT_IDENTIFIER:
		case TYPE_INFO_IDENTIFIER:
		{
			Decl *decl = sema_find_path_symbol(context, type_info->unresolved.name, type_info->unresolved.path);
			if (!decl) return NULL;
			if (!decl_ok(decl)) return poisoned_type;
			if (type_info->kind == TYPE_INFO_CT_IDENTIFIER) return decl->var.init_expr->type_expr->type->canonical;
			return decl->type->canonical;
		}
		case TYPE_INFO_VATYPE:
		{
			if (!context->current_macro) return NULL;
			Expr *arg_expr = sema_expr_analyse_ct_arg_index(context, type_info->unresolved_type_expr, NULL);
			if (!expr_ok(arg_expr)) return poisoned_type;
			if (!sema_analyse_expr(context, arg_expr)) return poisoned_type;
			if (arg_expr->expr_kind != EXPR_TYPEINFO) return NULL;
			return arg_expr->type_expr->type->canonical;
		}
		case TYPE_INFO_TYPEFROM:
		case TYPE_INFO_TYPEOF:
			if (!sema_resolve_type_info(context, type_info, RESOLVE_TYPE_DEFAULT)) return poisoned_type;
			return type_info->type;
		case TYPE_INFO_EVALTYPE:
		{
			Expr *expr = type_info->unresolved_type_expr;
			expr = sema_ct_eval_expr(context, true, expr, false);
			if (!expr_ok(expr)) return poisoned_type;
			if (!expr) return NULL;
			if (expr->expr_kind != EXPR_TYPEINFO)
			{
				SEMA_ERROR(expr, "Only type names may be resolved with $evaltype.");
				return poisoned_type;
			}
			type_info = expr->type_expr;
			goto RETRY;
		}
		case TYPE_INFO_SLICE:
		{
			// If it's an array, make sure we can resolve the length
			Type *type = sema_expr_check_type_exists(context, type_info->array.base);
			if (!type) return NULL;
			if (!type_ok(type)) return type;
			return type_get_slice(type);
		}
		case TYPE_INFO_INFERRED_ARRAY:
		{
			// If it's an array, make sure we can resolve the length
			Type *type = sema_expr_check_type_exists(context, type_info->array.base);
			if (!type) return NULL;
			if (!type_ok(type)) return type;
			return type_get_inferred_array(type);
		}
		case TYPE_INFO_INFERRED_VECTOR:
		{
			// If it's a vector, make sure we can resolve the length
			Type *type = sema_expr_check_type_exists(context, type_info->array.base);
			if (!type) return NULL;
			if (!type_ok(type)) return type;
			return type_get_inferred_vector(type);
		}
		case TYPE_INFO_POINTER:
		{
			// If it's an array, make sure we can resolve the length
			Type *type = sema_expr_check_type_exists(context, type_info->array.base);
			if (!type) return NULL;
			if (!type_ok(type)) return type;
			return type_get_ptr(type);
		}
	}
	UNREACHABLE
}


static inline bool sema_may_reuse_lambda(Decl *lambda, Type **types)
{
	Signature *sig = &lambda->func_decl.signature;
	if (typeget(sig->rtype)->canonical != types[0]) return false;
	FOREACH_IDX(i, Decl *, param, sig->params)
	{
		TypeInfo *info = vartype(param);
		ASSERT_SPAN(lambda, info && types[i + 1]); // NOLINT
		if (info->type->canonical != types[i + 1]) return false;
	}
	return true;
}

static inline Type *sema_evaluate_type_copy(SemaContext *context, TypeInfo *type_info)
{
	if (type_info->resolve_status == RESOLVE_DONE) return type_info->type;
	type_info = copy_type_info_single(type_info);
	if (!sema_resolve_type_info(context, type_info, RESOLVE_TYPE_DEFAULT)) return NULL;
	return type_info->type;
}

INLINE bool lambda_parameter_match(Decl **ct_lambda_params, Decl *candidate)
{
	unsigned param_count = vec_size(ct_lambda_params);
	if (vec_size(candidate->func_decl.lambda_ct_parameters) != param_count) return false;
	FOREACH_IDX(i, Decl *, param, candidate->func_decl.lambda_ct_parameters)
	{
		Decl *ct_param = ct_lambda_params[i];
		if (param->name != ct_param->name) return false;
		if (!param->var.is_read) continue;
		ASSERT(ct_param->resolve_status == RESOLVE_DONE || param->resolve_status == RESOLVE_DONE);
		ASSERT(ct_param->var.kind == param->var.kind);
		switch (ct_param->var.kind)
		{
			case VARDECL_LOCAL_CT_TYPE:
			case VARDECL_PARAM_CT_TYPE:
				if (ct_param->var.init_expr->const_expr.typeid->canonical !=
					param->var.init_expr->const_expr.typeid->canonical)
					return false;
				break;
			case VARDECL_LOCAL_CT:
			case VARDECL_PARAM_CT:
				if (!expr_is_const(ct_param->var.init_expr)) return false;
				if (!expr_is_const(param->var.init_expr)) return false;
				if (!expr_both_const_foldable(ct_param->var.init_expr, param->var.init_expr, BINARYOP_EQ)) return false;
				if (!expr_const_compare(&ct_param->var.init_expr->const_expr,
										&param->var.init_expr->const_expr, BINARYOP_EQ)) return false;
				break;
			default:
				UNREACHABLE
		}
	}
	return true;
}

static inline Decl *sema_find_cached_lambda(SemaContext *context, Type *func_type, Decl *original, Decl **ct_lambda_parameters)
{

	unsigned cached = vec_size(original->func_decl.generated_lambda);
	if (!cached) return NULL;
	// If it has a function type, then we just use that for comparison.
	if (func_type)
	{
		Type *raw = func_type->canonical->pointer->function.prototype->raw_type;
		FOREACH(Decl *, candidate, original->func_decl.generated_lambda)
		{
			if (raw == candidate->type->function.prototype->raw_type &&
				lambda_parameter_match(ct_lambda_parameters, candidate))
				return candidate;
		}
		return NULL;
	}
	Signature *sig = &original->func_decl.signature;
	if (!sig->rtype) return NULL;
	Type *rtype = sema_evaluate_type_copy(context, type_infoptr(sig->rtype));
	if (!rtype) return NULL;
	Type *types[200];
	types[0] = rtype;
	FOREACH_IDX(i, Decl *, param, sig->params)
	{
		TypeInfo *info = vartype(param);
		if (!info) return NULL;
		Type *type = sema_evaluate_type_copy(context, info);
		if (!type) return NULL;
		ASSERT(i < 198);
		types[i + 1] = type;
	}

	FOREACH(Decl *, candidate, original->func_decl.generated_lambda)
	{
		if (sema_may_reuse_lambda(candidate, types) &&
			lambda_parameter_match(ct_lambda_parameters, candidate))
			return candidate;
	}
	return NULL;
}

static inline bool sema_expr_analyse_embed(SemaContext *context, Expr *expr, bool allow_fail)
{
	Expr *filename = expr->embed_expr.filename;
	if (!sema_analyse_ct_expr(context, filename)) return false;
	Expr *len_expr = expr->embed_expr.len;
	size_t len = ~((size_t)0);
	if (len_expr)
	{
		if (!sema_analyse_ct_expr(context, len_expr)) return false;
		if (!expr_is_const_int(len_expr)) RETURN_SEMA_ERROR(len_expr, "Expected an integer value.");
		Int i = len_expr->const_expr.ixx;
		if (int_is_neg(i) || int_is_zero(i)) RETURN_SEMA_ERROR(len_expr, "Expected a positive value for the limit.");
		Int max = { .i.low = len, .type = TYPE_U64 };
		if (int_comp(i, max, BINARYOP_LT))
		{
			len = int_to_u64(i);
		}
	}
	if (!expr_is_const_string(filename)) RETURN_SEMA_ERROR(filename, "A compile time string was expected.");
	if (!filename->const_expr.bytes.len) RETURN_SEMA_ERROR(filename, "Expected a non-empty string.");
	CompilationUnit *unit = context->unit;
	const char *string = filename->const_expr.bytes.ptr;
	char *path;
	char *name;
	if (file_path_is_relative(string) && file_namesplit(unit->file->full_path, &name, &path))
	{
		string = file_append_path(path, string);
	}
	char *content = file_read_binary(string, &len);
	if (!content)
	{
		if (!allow_fail) RETURN_SEMA_ERROR(expr, "Failed to load '%s'.", string);
		if (!compiler.context.io_error_file_not_found)
		{
			Module *module = global_context_find_module(kw_std__io);
			Decl *io_error = module ? module_find_symbol(module, kw_FILE_NOT_FOUND) : NULL;
			Decl *fault = poisoned_decl;
			if (io_error && io_error->decl_kind == DECL_FAULT)
			{
				fault = io_error;
			}
			compiler.context.io_error_file_not_found = fault;
		}
		if (!decl_ok(compiler.context.io_error_file_not_found))
		{
			RETURN_SEMA_ERROR(expr, "Cannot generate an optional result, no IoError.FILE_NOT_FOUND could be located.");
		}
		expr->expr_kind = EXPR_OPTIONAL;
		expr->inner_expr = filename;
		filename->expr_kind = EXPR_CONST;
		filename->const_expr.const_kind = CONST_FAULT;
		expr->type = type_wildcard_optional;
		filename->const_expr.fault = compiler.context.io_error_file_not_found;
		filename->resolve_status = RESOLVE_DONE;
		expr->resolve_status = RESOLVE_DONE;
		return true;
	}
	expr->const_expr = (ExprConst){
			.const_kind = CONST_BYTES,
			.bytes.ptr = content,
			.bytes.len = len,
	};
	expr->expr_kind = EXPR_CONST;
	expr->resolve_status = RESOLVE_DONE;
	expr->type = type_get_slice(type_char);
	return true;
}

static inline bool sema_expr_analyse_generic_ident(SemaContext *context, Expr *expr)
{
	Expr *parent = exprptr(expr->generic_ident_expr.parent);
	if (parent->expr_kind != EXPR_UNRESOLVED_IDENTIFIER)
	{
		RETURN_SEMA_ERROR(parent, "Expected an identifier to parameterize.");
	}
	Decl *symbol = sema_analyse_parameterized_identifier(context, parent->unresolved_ident_expr.path,
	                                                     parent->unresolved_ident_expr.ident, parent->span,
	                                                     expr->generic_ident_expr.parameters, NULL, expr->span);
	if (!decl_ok(symbol)) return false;
	expr_resolve_ident(expr, symbol);
	return true;
}

static inline bool sema_expr_analyse_lambda(SemaContext *context, Type *target_type, Expr *expr)
{
	Decl *decl = expr->lambda_expr;
	if (!decl_ok(decl)) return false;
	assert(decl->resolve_status != RESOLVE_DONE);
	Type *flat = target_type ? type_flatten(target_type) : NULL;
	if (flat)
	{
		if (!type_is_func_ptr(flat))
		{
			RETURN_SEMA_ERROR(expr, "Can't convert a lambda to %s.", type_quoted_error_string(target_type));
		}
		if (!sema_resolve_type_decl(context, flat->pointer)) return false;
	}

	bool multiple = context->current_macro || context->ct_locals;

	// Capture CT variables
	Decl **ct_lambda_parameters = copy_decl_list_single(context->ct_locals);

	if (multiple && decl->resolve_status != RESOLVE_DONE)
	{
		Decl *decl_cached = sema_find_cached_lambda(context, flat, decl, ct_lambda_parameters);
		if (decl_cached)
		{
			if (context->unit->module != context->compilation_unit->module)
			{
				decl_cached->is_external_visible = true;
			}
			expr->type = type_get_func_ptr(decl_cached->type);
			expr_rewrite_const_ref(expr, decl_cached);
			return true;
		}
	}
	Decl *original = decl;
	if (multiple) decl = expr->lambda_expr = copy_lambda_deep(decl);
	Signature *sig = &decl->func_decl.signature;
	Signature *to_sig = flat ? flat->canonical->pointer->function.signature : NULL;
	if (sig->variadic == VARIADIC_RAW)
	{
		if (to_sig)
		{
			if (to_sig->variadic == VARIADIC_RAW)
			{
				RETURN_SEMA_ERROR(decl, "A lambda may not use C-style vaargs. Consequently it can never implement the %s function type.", type_quoted_error_string(flat));
			}
			RETURN_SEMA_ERROR(expr, "The lambda doesn't match the required type %s.", type_quoted_error_string(target_type));
		}
		RETURN_SEMA_ERROR(decl, "A lambda may not use C-style vaargs.");
	}
	if (!sig->rtype)
	{
		if (!to_sig) goto FAIL_NO_INFER;
		sig->rtype = type_info_id_new_base(typeget(to_sig->rtype), expr->span);
	}
	if (to_sig && vec_size(to_sig->params) != vec_size(sig->params))
	{
		RETURN_SEMA_ERROR(expr, "The lambda doesn't match the required type %s.", type_quoted_error_string(target_type));
	}
	FOREACH_IDX(i, Decl *, param, sig->params)
	{
		if (param->var.type_info) continue;
		if (!to_sig) goto FAIL_NO_INFER;
		param->var.type_info = type_info_id_new_base(to_sig->params[i]->type, param->span);
	}
	CompilationUnit *unit = decl->unit = context->unit;
	ASSERT_SPAN(expr, !decl->name);
	scratch_buffer_clear();
	switch (context->call_env.kind)
	{
		case CALL_ENV_GLOBAL_INIT:
			scratch_buffer_append(unit->module->name->module);
			scratch_buffer_append(".$global");
			break;
		case CALL_ENV_FUNCTION:
		case CALL_ENV_FUNCTION_STATIC:
			if (context->current_macro)
			{
				scratch_buffer_append(unit->module->name->module);
				scratch_buffer_append(".");
				scratch_buffer_append(context->current_macro->name);
			}
			else
			{
				scratch_buffer_append(context->call_env.current_function->unit->module->name->module);
				scratch_buffer_append(".");
				scratch_buffer_append(context->call_env.current_function->name);
			}
			break;
		case CALL_ENV_ATTR:
			scratch_buffer_append(unit->module->name->module);
			scratch_buffer_append(".$attr");
			break;
	}
	scratch_buffer_append("$lambda");
	scratch_buffer_append_unsigned_int(++unit->lambda_count);
	decl->name = scratch_buffer_copy();
	decl->extname = decl->name;
	decl->type = type_new_func(decl, sig);
	if (context->unit->module != context->compilation_unit->module)
	{
		decl->is_external_visible = true;
	}
	bool erase_decl = false;
	if (!sema_analyse_func_macro(context, decl, ATTR_FUNC, &erase_decl)) return false;
	if (erase_decl)
	{
		RETURN_SEMA_ERROR(decl, "`@if` can't be placed on a lambda.");
	}
	if (!sema_analyse_function_signature(context, decl, NULL, sig->abi, sig)) return false;
	if (flat && flat->pointer->function.prototype->raw_type != decl->type->function.prototype->raw_type)
	{
		RETURN_SEMA_ERROR(expr, "The lambda has type %s, which doesn't match the required type %s.",
						  type_quoted_error_string(decl->type),
						  type_quoted_error_string(target_type));
	}
	decl->func_decl.lambda_ct_parameters = ct_lambda_parameters;
	decl->func_decl.is_lambda = true;
	if (context_is_macro(context) || !context->call_env.current_function)
	{
		decl->func_decl.in_macro = true;
	}
	decl->alignment = type_alloca_alignment(decl->type);
	// We will actually compile this into any module using it (from a macro) by necessity,
	// so we'll declare it as weak and externally visible.
	unit_register_external_symbol(context, decl);

	// Before function analysis, lambda evaluation is deferred
	if (unit->module->stage < ANALYSIS_FUNCTIONS)
	{
		// Because we cannot check if the parameter is used before everything, set them all as read.
		FOREACH(Decl *, decl, ct_lambda_parameters)
		{
			decl->var.is_read = true;
		}
		decl->is_external_visible = true;
		vec_add(unit->module->lambdas_to_evaluate, decl);
	}
	else
	{
		SemaContext lambda_context;
		sema_context_init(&lambda_context, context->unit);
		if (sema_analyse_function_body(&lambda_context, decl))
		{
			vec_add(unit->lambdas, decl);
		}
		sema_context_destroy(&lambda_context);
	}

	expr->type = type_get_func_ptr(decl->type);
	expr->resolve_status = RESOLVE_DONE;
	expr_rewrite_const_ref(expr, decl);
	// If it's a distinct type we have to make a cast.
	if (target_type && expr->type != target_type && !cast_explicit(context, expr, target_type)) return false;
	if (multiple)
	{
		vec_add(original->func_decl.generated_lambda, decl);
	}
	decl->resolve_status = RESOLVE_DONE;
	return true;
FAIL_NO_INFER:
	RETURN_SEMA_ERROR(expr, "Inferred lambda expressions cannot be used unless the type can be determined.");
}

static inline bool sema_expr_analyse_ct_feature(SemaContext *context, Expr *expr)
{
	if (expr->resolve_status == RESOLVE_DONE) return expr_ok(expr);

	Expr *inner = expr->ct_call_expr.main_var;
	if (expr->ct_call_expr.flat_path) goto ERROR;
	if (inner->expr_kind != EXPR_UNRESOLVED_IDENTIFIER) goto ERROR;
	if (!inner->unresolved_ident_expr.is_const) goto ERROR;

	const char *name = inner->unresolved_ident_expr.ident;
	void *value = htable_get(&compiler.context.features, (void *)name);
	ASSERT_SPAN(expr, !value || value == name);
	expr_rewrite_const_bool(expr, type_bool, value != NULL);
	return true;
ERROR:
	RETURN_SEMA_ERROR(inner, "Expected a feature name here, e.g. $feature(MY_FEATURE).");
}

static inline bool sema_expr_analyse_ct_is_const(SemaContext *context, Expr *expr)
{
	SEMA_DEPRECATED(expr, "$is_const is deprecated, use '$defined(var $e = expr)' instead.");
	ASSERT_SPAN(expr, expr->resolve_status == RESOLVE_RUNNING);
	Expr *inner = expr->inner_expr;
	if (!sema_analyse_expr_rvalue(context, inner)) return false;
	expr_rewrite_const_bool(expr, type_bool, sema_cast_const(inner));
	return true;
}
static bool sema_expr_analyse_lenof(SemaContext *context, Expr *expr, bool *missing_ref)
{
	Expr *inner = expr->inner_expr;
	if (!sema_analyse_expr_rvalue(context, inner)) return false;
	Type *canonical = inner->type->canonical;
	Decl *len = sema_find_untyped_operator(canonical, OVERLOAD_LEN, NULL);
	if (len)
	{
		return sema_insert_method_call(context, expr, len, inner, NULL, false);
	}
	switch (canonical->type_kind)
	{
		case TYPE_ARRAY:
		case TYPE_VECTOR:
			expr_rewrite_const_int(expr, type_isz, canonical->array.len);
			return true;
		case TYPE_SLICE:
			expr_rewrite_slice_len(expr, inner, type_isz);
			return true;
		default:
			if (missing_ref)
			{
				*missing_ref = true;
				return false;
			}
			RETURN_SEMA_ERROR(inner, "%s does not support lengthof()", type_quoted_error_string(inner->type));
	}
}

static inline bool sema_expr_analyse_ct_defined(SemaContext *context, Expr *expr)
{
	if (expr->resolve_status == RESOLVE_DONE) return expr_ok(expr);

	Expr **list = expr->expression_list;

	bool success = true;
	bool failed = false;
	unsigned list_len = vec_size(list);
	if (!list_len) RETURN_SEMA_ERROR(expr, "Expected at least one expression to test.");
	for (unsigned i = 0; i < list_len; i++)
	{
		Expr *main_expr = list[i];
		SemaContext *active_context = context;
		bool in_no_eval = active_context->call_env.in_no_eval;
		active_context->call_env.in_no_eval = true;
		bool unroll_hash = false;
	RETRY:
		switch (main_expr->expr_kind)
		{
			case EXPR_OTHER_CONTEXT:
				active_context->call_env.in_no_eval = in_no_eval;
				active_context = main_expr->expr_other_context.context;
				in_no_eval = active_context->call_env.in_no_eval;
				active_context->call_env.in_no_eval = true;
				main_expr = main_expr->expr_other_context.inner;
				goto RETRY;
		case EXPR_LENGTHOF:
				if (!sema_expr_analyse_lenof(active_context, main_expr, &failed))
				{
					if (!failed) goto FAIL;
					success = false;
				}
				break;
			case EXPR_ACCESS_UNRESOLVED:
				if (!sema_expr_analyse_access(active_context, main_expr, &failed))
				{
					if (!failed) goto FAIL;
					success = false;
				}
				break;
		case EXPR_UNRESOLVED_IDENTIFIER:
		{
			Decl *decl = sema_find_path_symbol(active_context, main_expr->unresolved_ident_expr.ident, main_expr->unresolved_ident_expr.path);
			if (!decl_ok(decl)) goto FAIL;
			success = decl != NULL;
			break;
		}
		case EXPR_HASH_IDENT:
		{
			if (unroll_hash)
			{
				Decl *decl = sema_resolve_symbol(active_context, main_expr->hash_ident_expr.identifier, NULL, main_expr->span);
				if (!decl) goto FAIL;
				bool is_ref = main_expr->hash_ident_expr.is_ref;
				main_expr = copy_expr_single(decl->var.init_expr);
				if (is_ref) expr_set_to_ref(main_expr);
				goto RETRY;
			}
			Decl *decl = sema_find_symbol(active_context, main_expr->hash_ident_expr.identifier);
			if (!decl_ok(decl)) goto FAIL;
			success = decl != NULL;
			break;
		}
		case EXPR_COMPILER_CONST:
				success = sema_expr_analyse_compiler_const(active_context, main_expr, false);
				break;
		case EXPR_BUILTIN:
				success = sema_expr_analyse_builtin(active_context, main_expr, false);
				break;
		case EXPR_UNARY:
				main_expr->resolve_status = RESOLVE_RUNNING;
				if (!sema_expr_analyse_unary(active_context, main_expr, &failed))
				{
					if (!failed) goto FAIL;
					success = false;
				}
				break;
		case EXPR_TYPEINFO:
		{
			Type *type = sema_expr_check_type_exists(active_context, main_expr->type_expr);
			if (!type_ok(type)) goto FAIL;
			success = type != NULL;
			break;
		}
		case EXPR_CT_EVAL:
		{
			Expr *eval = sema_ct_eval_expr(active_context, "$eval", main_expr->inner_expr, false);
			if (!expr_ok(eval)) return false;
			if (eval)
			{
				main_expr = eval;
				goto RETRY;
			}
			success = false;
			break;
		}
		case EXPR_SUBSCRIPT:
		{
			if (!sema_expr_analyse_subscript(active_context, main_expr, &failed))
			{
				if (!failed) goto FAIL;
				success = false;
			}
			break;
		}
		case EXPR_CAST:
		{
			TypeInfo *typeinfo = type_infoptr(main_expr->cast_expr.type_info);
			if (typeinfo->resolve_status == RESOLVE_DONE && typeinfo->type == type_void)
			{
				main_expr = exprptr(main_expr->cast_expr.expr);
				unroll_hash = true;
				goto RETRY;
			}
			if (!sema_expr_analyse_cast(active_context, main_expr, &failed))
			{
				if (!failed) goto FAIL;
				success = false;
			}
			break;
		}
		case EXPR_CT_IDENT:
		{
			Decl *decl = sema_find_symbol(active_context, main_expr->ct_ident_expr.identifier);
			if (!decl_ok(decl)) goto FAIL;
			success = decl != NULL;
			break;
		}
		case EXPR_CALL:
		{
			bool no_match;
			if (!sema_expr_analyse_call(active_context, main_expr, &no_match))
			{
				if (!no_match) goto FAIL;
				success = false;
			}
			break;
		}
		case EXPR_FORCE_UNWRAP:
				if (!sema_analyse_expr_rvalue(active_context, main_expr->inner_expr)) goto FAIL;
				success = IS_OPTIONAL(main_expr->inner_expr);
				break;
		case EXPR_RETHROW:
				if (!sema_analyse_expr_rvalue(active_context, main_expr->rethrow_expr.inner)) goto FAIL;
				success = IS_OPTIONAL(main_expr->rethrow_expr.inner);
				break;
		case EXPR_OPTIONAL:
				if (!sema_expr_analyse_optional(active_context, main_expr, &failed))
				{
					if (!failed) goto FAIL;
					success = false;
				}
				break;
		case EXPR_POISONED:
				success = false;
				break;
		case EXPR_CATCH_UNRESOLVED:
		case EXPR_CATCH:
		case EXPR_COND:
		case EXPR_TEST_HOOK:
		case EXPR_DESIGNATOR:
		case EXPR_BENCHMARK_HOOK:
		case EXPR_TRY_UNRESOLVED:
		case EXPR_TRY:
		case EXPR_TRY_UNWRAP_CHAIN:
		case EXPR_OPERATOR_CHARS:
		case EXPR_MACRO_BODY_EXPANSION:
		case EXPR_BUILTIN_ACCESS:
		case EXPR_LAST_FAULT:
		case EXPR_DEFAULT_ARG:
		case EXPR_IDENTIFIER:
		case EXPR_NAMED_ARGUMENT:
		case EXPR_ACCESS_RESOLVED:
		case EXPR_CT_SUBSCRIPT:
		case EXPR_IOTA_DECL:
				UNREACHABLE
			case EXPR_DECL:
				if (!sema_analyse_var_decl(context, main_expr->decl_expr, true, &failed))
				{
					if (!failed) goto FAIL;
					success = false;
				}
				break;
		case EXPR_BINARY:
				main_expr->resolve_status = RESOLVE_RUNNING;
				if (!sema_expr_analyse_binary(active_context, NULL, main_expr, &failed))
				{
					if (!failed) goto FAIL;
					success = false;
				}
				break;
		case EXPR_CT_CALL:
				main_expr->resolve_status = RESOLVE_RUNNING;
				if (!sema_expr_analyse_ct_call(active_context, main_expr, &failed))
				{
					if (!failed) goto FAIL;
					success = false;
				}
				break;
		case EXPR_COMPOUND_LITERAL:
				if (!sema_expr_analyse_compound_literal(context, main_expr, &failed))
				{
					if (!failed) goto FAIL;
					success = false;
				}
				break;
		case EXPR_CT_ARG:
				if (!sema_expr_analyse_ct_arg(context, NULL, main_expr, &failed))
				{
					if (!failed) goto FAIL;
					success = false;
				}
				break;
			case EXPR_BITACCESS:
			case EXPR_BITASSIGN:
			case EXPR_EMBED:
			case EXPR_GENERIC_IDENT:
			case EXPR_MACRO_BODY:
			case EXPR_POINTER_OFFSET:
			case EXPR_RETVAL:
			case EXPR_SLICE:
			case EXPR_SLICE_ASSIGN:
			case EXPR_SLICE_COPY:
			case EXPR_SUBSCRIPT_ADDR:
			case EXPR_SUBSCRIPT_ASSIGN:
			case EXPR_SWIZZLE:
			case EXPR_VASPLAT:
				REMINDER("Check if these should be analysed");
				FALLTHROUGH;
				// Above needs to be analysed
			case EXPR_INITIALIZER_LIST:
			case EXPR_DESIGNATED_INITIALIZER_LIST:
			case EXPR_ASM:
			case EXPR_CONST:
			case EXPR_NOP:
			case EXPR_MACRO_BLOCK:
			case EXPR_LAMBDA:
			case EXPR_CT_IS_CONST:
			case EXPR_CT_DEFINED:
			case EXPR_STRINGIFY:
			case EXPR_TERNARY:
			case EXPR_CT_ASSIGNABLE:
			case EXPR_EXPRESSION_LIST:
			case EXPR_POST_UNARY:
			case EXPR_TYPEID:
			case EXPR_TYPEID_INFO:
			case EXPR_TYPECALL:
			case EXPR_MEMBER_GET:
			case EXPR_MEMBER_SET:
			case EXPR_SPLAT:
			case EXPR_EXT_TRUNC:
			case EXPR_INT_TO_BOOL:
			case EXPR_VECTOR_TO_ARRAY:
			case EXPR_SLICE_TO_VEC_ARRAY:
			case EXPR_SCALAR_TO_VECTOR:
			case EXPR_PTR_ACCESS:
			case EXPR_ENUM_FROM_ORD:
			case EXPR_SLICE_LEN:
			case EXPR_VECTOR_FROM_ARRAY:
			case EXPR_RVALUE:
			case EXPR_RECAST:
			case EXPR_MAKE_ANY:
			case EXPR_DISCARD:
			case EXPR_ADDR_CONVERSION:
			case EXPR_FLOAT_TO_INT:
			case EXPR_INT_TO_FLOAT:
			case EXPR_INT_TO_PTR:
			case EXPR_PTR_TO_INT:
			case EXPR_MAKE_SLICE:
			case EXPR_TWO:
				if (!sema_analyse_expr_rvalue(active_context, main_expr)) goto FAIL;
				break;

		}
		active_context->call_env.in_no_eval = in_no_eval;
		if (success) continue;
		break;
FAIL:
		active_context->call_env.in_no_eval = in_no_eval;
		return false;
	}
	expr_rewrite_const_bool(expr, type_bool, success);
	return true;
}

static inline bool sema_expr_analyse_ct_arg(SemaContext *context, Type *infer_type, Expr *expr, bool *no_match_ref)
{
	ASSERT_SPAN(expr, expr->resolve_status == RESOLVE_RUNNING);
	TokenType type = expr->ct_arg_expr.type;
	if (!context->macro_has_vaargs)
	{
		RETURN_SEMA_ERROR(expr, "'%s' can only be used inside of a macro with untyped vaargs.", token_type_to_string(type));
	}
	switch (type)
	{
		case TOKEN_CT_VACOUNT:
			expr_rewrite_const_int(expr, type_usz, vec_size(context->macro_varargs));
			return true;
		case TOKEN_CT_VAARG:
		{
			unsigned index = 0;
			// A normal argument, this means we only evaluate it once.
			ASSIGN_EXPR_OR_RET(Expr *arg_expr, sema_expr_analyse_ct_arg_index(context, exprptr(expr->ct_arg_expr.arg), &index), false);

			index++;
			ASSERT_SPAN(expr, index < 0x10000);
			Decl *decl = NULL;
			// Try to find the original param.
			FOREACH(Decl *, val, context->macro_params)
			{
				if (!val) continue;
				if (val->var.va_index == index && val->var.kind == VARDECL_PARAM)
				{
					decl = val;
					break;
				}
			}
			// Not found, so generate a new.
			if (!decl)
			{
				if (!sema_analyse_inferred_expr(context, infer_type, arg_expr, no_match_ref)) return false;
				switch (sema_resolve_storage_type(context, arg_expr->type))
				{
					case STORAGE_ERROR:
						return false;
					case STORAGE_NORMAL:
						break;
					default:
						RETURN_SEMA_ERROR(expr, "The vararg doesn't have a valid runtime type.");
				}
				decl = decl_new_generated_var(arg_expr->type, VARDECL_PARAM, arg_expr->span);
				decl->var.init_expr = arg_expr;
				decl->var.va_index = (uint16_t)index;
				vec_add(context->macro_params, decl);
			}
			// Replace with the identifier.
			expr->expr_kind = EXPR_IDENTIFIER;
			expr_resolve_ident(expr, decl);
			ASSERT_SPAN(expr, expr->type);
			return true;
		}
		case TOKEN_CT_VAEXPR:
		{
			// An expr argument, this means we copy and evaluate.
			ASSIGN_EXPR_OR_RET(Expr *arg_expr, sema_expr_analyse_ct_arg_index(context, exprptr(expr->ct_arg_expr.arg), NULL), false);
			expr_replace(expr, copy_expr_single(arg_expr));
			return sema_analyse_inferred_expr(context, infer_type, expr, no_match_ref);
		}
		case TOKEN_CT_VACONST:
		{
			// An expr argument, this means we copy and evaluate.
			ASSIGN_EXPR_OR_RET(Expr *arg_expr, sema_expr_analyse_ct_arg_index(context, exprptr(expr->ct_arg_expr.arg), NULL), false);
			arg_expr = copy_expr_single(arg_expr);
			if (!sema_analyse_inferred_expr(context, infer_type, arg_expr, NULL)) return false;
			if (!sema_cast_const(arg_expr))
			{
				if (no_match_ref) goto NO_MATCH;
				RETURN_SEMA_ERROR(arg_expr, "This argument needs to be a compile time constant.");
			}
			expr_replace(expr, arg_expr);
			return true;
		}
		case TOKEN_CT_VATYPE:
		default:
			UNREACHABLE
	}
NO_MATCH:
	*no_match_ref = true;
	return false;
}

static inline bool sema_expr_analyse_iota_decl(SemaContext *context, Expr *expr)
{
	Decl *decl = expr->iota_decl_expr;
	if (!decl_ok(decl)) return false;
	if (decl->resolve_status != RESOLVE_DONE)
	{
		RETURN_SEMA_ERROR(expr, "Enum constants values should never need to be resolved out of order.");
	}
	Expr *iota_expr = decl->enum_constant.value;
	assert(expr_is_const_int(iota_expr));

	Int value = iota_expr->const_expr.ixx;
	Int add = int_add64(value, 1);
	if (int_comp(add, value, BINARYOP_LT))
	{
		RETURN_SEMA_ERROR(expr, "Enum value would overflow the maximum value of the container type %s.", type_quoted_error_string(type_flatten(decl->type)));
	}
	expr_rewrite_const_integer(expr, iota_expr->type, add.i);
	return true;
}

static inline bool sema_expr_analyse_assignable(SemaContext *context, Expr *expr)
{
	SEMA_DEPRECATED(expr, "$assignable is deprecated, use '$defined(Type t = expr)' instead.");
	ASSERT_SPAN(expr, expr->resolve_status == RESOLVE_RUNNING);
	Expr *type_expr = exprptr(expr->assignable_expr.type);
	bool in_no_eval = context->call_env.in_no_eval;
	context->call_env.in_no_eval = true;
	Type *type;
	if (type_expr->expr_kind == EXPR_TYPEINFO)
	{
		TypeInfo *type_info = type_expr->type_expr;
		if (!sema_resolve_type_info(context, type_info, RESOLVE_TYPE_ALLOW_INFER)) goto FAILED;
		type = type_info->type;
	}
	else
	{
		if (!sema_analyse_expr(context, type_expr)) goto FAILED;
		switch (type_expr->expr_kind)
		{
			case EXPR_TYPEINFO:
				ASSERT_SPAN(expr, type_expr->type_expr->resolve_status == RESOLVE_DONE);
				type = type_expr->type_expr->type;
				break;
			case EXPR_CONST:
				if (type_expr->const_expr.const_kind == CONST_TYPEID)
				{
					type = type_expr->const_expr.typeid;
					break;
				}
				type = NULL;
				break;
			default:
				type = NULL;
				break;
		}
	}
	if (!type) RETURN_SEMA_ERROR(type_expr, "Expected a type or constant typeid here.");
	Expr *inner = exprptr(expr->assignable_expr.expr);
	if (!sema_analyse_inferred_expr(context, type, inner, NULL)) goto FAILED;
	bool ok = may_cast(context, inner, type, false, true);
	expr_rewrite_const_bool(expr, type_bool, ok);
	context->call_env.in_no_eval = in_no_eval;
	return true;
FAILED:
	context->call_env.in_no_eval = in_no_eval;
	return false;
}


static inline bool sema_expr_analyse_ct_stringify(SemaContext *context, Expr *expr)
{
	Expr *inner = expr->inner_expr;
	// Only hash ident style stringify reaches here.
	while (true)
	{
		switch (inner->expr_kind)
		{
			case EXPR_CT_ARG:
			{
				ASSIGN_EXPR_OR_RET(Expr *arg_expr, sema_expr_analyse_ct_arg_index(context, exprptr(inner->ct_arg_expr.arg), NULL), false);
				inner = arg_expr;
				continue;
			}
			case EXPR_OTHER_CONTEXT:
				context = inner->expr_other_context.context;
				inner = inner->expr_other_context.inner;
				continue;
			case EXPR_HASH_IDENT:
			{
				Decl *decl = sema_resolve_symbol(context, inner->ct_ident_expr.identifier, NULL, inner->span);
				if (!decl) return false;
				inner = decl->var.init_expr;
				continue;
			}
			default:
				break;
		}
		break;
	}
	const char *desc = span_to_string(inner->span);
	if (!desc)
	{
		RETURN_SEMA_ERROR(expr, "Failed to stringify hash variable contents - they must be a single line and not exceed 255 characters.");
	}
	expr_rewrite_const_string(expr, desc);
	return true;
}

static inline bool sema_expr_resolve_ct_eval(SemaContext *context, Expr *expr)
{
	Expr *result = sema_ct_eval_expr(context, false, expr->inner_expr, true);
	if (!result) return false;
	if (result->expr_kind == EXPR_TYPEINFO)
	{
		RETURN_SEMA_ERROR(result, "Evaluation to a type requires the use of '$typefrom' rather than '$eval'.");
	}
	expr_replace(expr, result);
	return true;
}

static inline bool sema_expr_analyse_ct_offsetof(SemaContext *context, Expr *expr, bool *failed_ref)
{
	Expr *main_var = expr->ct_call_expr.main_var;
	Decl *decl = sema_expr_analyse_var_path(context, main_var, failed_ref);
	if (!decl) return false;
	DesignatorElement **path = expr->ct_call_expr.flat_path;
	if (!vec_size(path))
	{
		RETURN_SEMA_ERROR(expr, "Expected a path to get the offset of.");
	}

	ByteSize offset = 0;
	Type *type = decl->type;
	FOREACH_IDX(i, DesignatorElement *, element, path)
	{
		Decl *member;
		ArraySize index = 0;
		Type *result_type;
		if (!sema_expr_analyse_decl_element(context,
											element,
											type,
											&member,
											&index,
											&result_type,
											i,
											i == 0 ? main_var->span : expr->span,
											NULL)) return false;
		if (member)
		{
			offset += member->offset;
		}
		else
		{
			offset += (ByteSize)(type_size(result_type) * index);
		}
		type = result_type;
	}

	expr_rewrite_const_int(expr, type_isz, offset);

	return true;
}

static inline bool sema_expr_analyse_ct_call(SemaContext *context, Expr *expr, bool *failed_ref)
{
	switch (expr->ct_call_expr.token_type)
	{
		case TOKEN_CT_DEFINED:
			return sema_expr_analyse_ct_defined(context, expr);
		case TOKEN_CT_ALIGNOF:
			return sema_expr_analyse_ct_alignof(context, expr, failed_ref);
		case TOKEN_CT_OFFSETOF:
			return sema_expr_analyse_ct_offsetof(context, expr, failed_ref);
		case TOKEN_CT_QNAMEOF:
		case TOKEN_CT_NAMEOF:
		case TOKEN_CT_EXTNAMEOF:
			return sema_expr_analyse_ct_nameof(context, expr, failed_ref);
		case TOKEN_CT_FEATURE:
			return sema_expr_analyse_ct_feature(context, expr);
		default:
			UNREACHABLE
	}
}


static inline BuiltinFunction builtin_by_name(const char *name)
{
	for (unsigned i = 0; i < NUMBER_OF_BUILTINS; i++)
	{
		if (builtin_list[i] == name) return (BuiltinFunction)i;
	}
	return BUILTIN_NONE;
}


static inline bool sema_expr_analyse_retval(SemaContext *context, Expr *expr)
{
	if ((context->active_scope.flags & (SCOPE_ENSURE | SCOPE_ENSURE_MACRO)) == 0)
	{
		RETURN_SEMA_ERROR(expr, "'return' as a value can only be used inside of an '@ensure'.");
	}
	Expr *return_value = context->return_expr;
	bool is_macro_ensure = (context->active_scope.flags & SCOPE_ENSURE_MACRO) != 0;
	if (is_macro_ensure)
	{
		expr->type = type_no_optional(return_value->type);
	}
	else
	{
		expr->type = type_no_optional(context->rtype);
		if (type_is_void(expr->type))
		{
			RETURN_SEMA_ERROR(expr, "'return' cannot be used on void functions.");
		}
	}
	ASSERT_SPAN(expr, return_value);
	if (expr_is_runtime_const(return_value))
	{
		expr_replace(expr, copy_expr_single(return_value));
	}
	return true;
}

static inline bool sema_expr_analyse_builtin(SemaContext *context, Expr *expr, bool throw_error)
{
	const char *builtin_char = expr->builtin_expr.ident;
	BuiltinFunction func = builtin_by_name(builtin_char);
	if (func == BUILTIN_NONE)
	{
		if (throw_error) SEMA_ERROR(expr, "Unsupported builtin '%s'.", builtin_char);
		return false;
	}
	expr->builtin_expr.builtin = func;
	return true;
}

static inline bool sema_expr_analyse_compound_literal(SemaContext *context, Expr *expr, bool *no_match_ref)
{
	TypeInfo *type_info = expr->expr_compound_literal.type_info;
	// We allow inferring the size of arrays.
	if (!sema_resolve_type_info(context, type_info, RESOLVE_TYPE_ALLOW_INFER)) return false;
	Type *type = type_info->type;
	if (type_is_optional(type))
	{
		RETURN_SEMA_ERROR(type_info, "The type here should always be written as a plain type and not an optional, please remove the '?'.");
	}
	if (!sema_resolve_type_structure(context, type)) return false;
	if (!sema_expr_analyse_initializer_list(context, type, expr->expr_compound_literal.initializer, no_match_ref)) return false;
	expr_replace(expr, expr->expr_compound_literal.initializer);
	return true;
}


static inline bool sema_analyse_expr_dispatch(SemaContext *context, Expr *expr)
{
	switch (expr->expr_kind)
	{
		case EXPR_ASM:
		case EXPR_BENCHMARK_HOOK:
		case EXPR_CATCH_UNRESOLVED:
		case EXPR_CATCH:
		case EXPR_COND:
		case EXPR_DEFAULT_ARG:
		case EXPR_DESIGNATOR:
		case EXPR_MACRO_BODY:
		case EXPR_MACRO_BODY_EXPANSION:
		case EXPR_MEMBER_GET:
		case EXPR_MEMBER_SET:
		case EXPR_NAMED_ARGUMENT:
		case EXPR_NOP:
		case EXPR_OPERATOR_CHARS:
		case EXPR_SWIZZLE:
		case EXPR_TEST_HOOK:
		case EXPR_TRY_UNRESOLVED:
		case EXPR_TRY:
		case EXPR_TRY_UNWRAP_CHAIN:
		case EXPR_TYPEID_INFO:
		case EXPR_FLOAT_TO_INT:
		case EXPR_INT_TO_FLOAT:
		case EXPR_INT_TO_PTR:
		case EXPR_PTR_TO_INT:
		case EXPR_ENUM_FROM_ORD:
		case EXPR_VECTOR_TO_ARRAY:
		case EXPR_SLICE_TO_VEC_ARRAY:
		case EXPR_SCALAR_TO_VECTOR:
		case EXPR_MAKE_SLICE:
		case EXPR_CT_SUBSCRIPT:
			UNREACHABLE
		case EXPR_LENGTHOF:
			return sema_expr_analyse_lenof(context, expr, NULL);
		case EXPR_IOTA_DECL:
			return sema_expr_analyse_iota_decl(context, expr);
		case EXPR_TWO:
			if (!sema_analyse_expr_rvalue(context, expr->two_expr.first)) return false;
			if (!sema_analyse_expr(context, expr->two_expr.last)) return false;
			expr->type = expr->two_expr.last->type;
			return true;
		case EXPR_MAKE_ANY:
			if (!sema_analyse_expr_rvalue(context, expr->make_any_expr.typeid)) return false;
			return sema_analyse_expr_rvalue(context, expr->make_any_expr.inner);
		case EXPR_RVALUE:
		case EXPR_RECAST:
		case EXPR_ADDR_CONVERSION:
		case EXPR_DISCARD:
			return sema_analyse_expr_rvalue(context, expr->inner_expr);
		case EXPR_PTR_ACCESS:
		case EXPR_SLICE_LEN:
		case EXPR_VECTOR_FROM_ARRAY:
			return sema_analyse_expr_rvalue(context, expr->inner_expr);
		case EXPR_INT_TO_BOOL:
			return sema_analyse_expr_rvalue(context, expr->int_to_bool_expr.inner);
		case EXPR_EXT_TRUNC:
			return sema_analyse_expr_rvalue(context, expr->ext_trunc_expr.inner);
		case EXPR_SPLAT:
			RETURN_SEMA_ERROR(expr, "Splat ('...') may only appear in initializers and calls.");
		case EXPR_TYPECALL:
			RETURN_SEMA_ERROR(expr, "Expected '()' after this.");
		case EXPR_OTHER_CONTEXT:
		{
			SemaContext *new_context = expr->expr_other_context.context;
			expr_replace(expr, expr->expr_other_context.inner);
			if (expr->resolve_status == RESOLVE_DONE) return expr_ok(expr);
			ContextSwitchState state = context_switch_state_push(context, new_context);
			expr->resolve_status = RESOLVE_RUNNING;
			bool success = sema_analyse_expr_dispatch(new_context, expr);
			context_switch_stat_pop(new_context, state);
			return success;
		}
		case EXPR_CT_ASSIGNABLE:
			return sema_expr_analyse_assignable(context, expr);
		case EXPR_EMBED:
			return sema_expr_analyse_embed(context, expr, false);
		case EXPR_VASPLAT:
			RETURN_SEMA_ERROR(expr, "'$vasplat' can only be used inside of macros.");
		case EXPR_GENERIC_IDENT:
			return sema_expr_analyse_generic_ident(context, expr);
		case EXPR_LAMBDA:
			return sema_expr_analyse_lambda(context, NULL, expr);
		case EXPR_CT_IS_CONST:
			return sema_expr_analyse_ct_is_const(context, expr);
		case EXPR_CT_DEFINED:
			return sema_expr_analyse_ct_defined(context, expr);
		case EXPR_CT_ARG:
			return sema_expr_analyse_ct_arg(context, NULL, expr, NULL);
		case EXPR_STRINGIFY:
			if (!sema_expr_analyse_ct_stringify(context, expr)) return false;
			return true;
		case EXPR_DECL:
		{
			Decl *decl = expr->decl_expr;
			bool erase = decl->var.kind == VARDECL_LOCAL_CT_TYPE || decl->var.kind == VARDECL_LOCAL_CT;
			if (!sema_analyse_var_decl(context, decl, true, NULL)) return false;
			if (decl->decl_kind == DECL_ERASED)
			{
				expr->expr_kind = EXPR_NOP;
				expr->type = type_void;
				return true;
			}
			if (erase)
			{
				Expr *init = decl->var.init_expr;
				if (init)
				{
					expr_replace(expr, copy_expr_single(decl->var.init_expr));
					return true;
				}
				expr->expr_kind = EXPR_NOP;
				expr->type = type_void;
				return true;
			}
			expr->type = decl->type;
			return true;
		}
		case EXPR_LAST_FAULT:
			expr->type = type_fault;
			return true;
		case EXPR_RETVAL:
			return sema_expr_analyse_retval(context, expr);
		case EXPR_BUILTIN:
			return sema_expr_analyse_builtin(context, expr, true);
		case EXPR_CT_CALL:
			return sema_expr_analyse_ct_call(context, expr, NULL);
		case EXPR_HASH_IDENT:
			if (!sema_expr_fold_hash(context, expr)) return false;
			return sema_analyse_expr(context, expr);
		case EXPR_CT_IDENT:
			return sema_expr_analyse_ct_identifier(context, expr);
		case EXPR_OPTIONAL:
			return sema_expr_analyse_optional(context, expr, NULL);
		case EXPR_COMPILER_CONST:
			return sema_expr_analyse_compiler_const(context, expr, true);
		case EXPR_POINTER_OFFSET:
			return sema_expr_analyse_pointer_offset(context, expr);
		case EXPR_POISONED:
			return false;
		case EXPR_SLICE_ASSIGN:
		case EXPR_BUILTIN_ACCESS:
		case EXPR_SLICE_COPY:
		case EXPR_BITASSIGN:
			// Created during semantic analysis
			UNREACHABLE
		case EXPR_MACRO_BLOCK:
			UNREACHABLE
		case EXPR_TYPEINFO:
			expr->type = type_typeinfo;
			return sema_resolve_type_info(context, expr->type_expr, RESOLVE_TYPE_DEFAULT);
		case EXPR_SLICE:
			return sema_expr_analyse_slice(context, expr);
		case EXPR_FORCE_UNWRAP:
			return sema_expr_analyse_force_unwrap(context, expr);
		case EXPR_COMPOUND_LITERAL:
			return sema_expr_analyse_compound_literal(context, expr, NULL);
		case EXPR_RETHROW:
			return sema_expr_analyse_rethrow(context, expr, NULL);
		case EXPR_CONST:
			return true;
		case EXPR_CT_EVAL:
			if (!sema_expr_resolve_ct_eval(context, expr)) return false;
			return sema_analyse_expr_dispatch(context, expr);
		case EXPR_BINARY:
			return sema_expr_analyse_binary(context, NULL, expr, NULL);
		case EXPR_TERNARY:
			return sema_expr_analyse_ternary(context, NULL, expr);
		case EXPR_UNARY:
		case EXPR_POST_UNARY:
			return sema_expr_analyse_unary(context, expr, NULL);
		case EXPR_TYPEID:
			return sema_expr_analyse_typeid(context, expr);
		case EXPR_IDENTIFIER:
			UNREACHABLE
		case EXPR_UNRESOLVED_IDENTIFIER:
			return sema_expr_analyse_identifier(context, NULL, expr);
		case EXPR_CALL:
			return sema_expr_analyse_call(context, expr, NULL);
		case EXPR_SUBSCRIPT:
		case EXPR_SUBSCRIPT_ADDR:
			return sema_expr_analyse_subscript(context, expr, NULL);
		case EXPR_BITACCESS:
		case EXPR_SUBSCRIPT_ASSIGN:
		case EXPR_ACCESS_RESOLVED:
			UNREACHABLE
		case EXPR_ACCESS_UNRESOLVED:
			return sema_expr_analyse_access(context, expr, NULL);
		case EXPR_INITIALIZER_LIST:
		case EXPR_DESIGNATED_INITIALIZER_LIST:
			return sema_expr_analyse_initializer_list(context, type_untypedlist, expr, NULL);
		case EXPR_CAST:
			return sema_expr_analyse_cast(context, expr, NULL);
		case EXPR_EXPRESSION_LIST:
			return sema_expr_analyse_expr_list(context, expr);
	}
	UNREACHABLE
}


bool sema_analyse_cond_expr(SemaContext *context, Expr *expr, CondResult *result)
{
	if (expr_is_ungrouped_binary(expr) && expr->binary_expr.operator == BINARYOP_ASSIGN)
	{
		RETURN_SEMA_ERROR(expr, "Assignment expressions must be enclosed in an extra () in conditionals.");
	}
	if (!sema_analyse_expr_rvalue(context, expr)) return false;
	if (IS_OPTIONAL(expr))
	{
		RETURN_SEMA_ERROR(expr, "An optional %s cannot be implicitly converted to a regular boolean value, "
								"use '@ok(<expr>)' and '@catch(<expr>)' to conditionally execute on success "
								"or failure.",
								type_quoted_error_string(expr->type));
	}
	if (!cast_explicit(context, expr, type_bool)) return false;
	if (sema_cast_const(expr) && expr_is_const_bool(expr))
	{
		*result = expr->const_expr.b ? COND_TRUE : COND_FALSE;
	}
	return true;
}

static inline bool sema_analyse_expr_rhs_param(SemaContext *context, Type *to, Expr *expr, bool *no_match_ref)
{
	Module *generic_module = context->generic.infer;
	context->generic.infer = to ? type_find_generic(to) : NULL;
	bool success = sema_analyse_expr_rhs(context, to, expr, true, no_match_ref, false);
	context->generic.infer = generic_module;
	return success;
}

bool sema_analyse_expr_rhs(SemaContext *context, Type *to, Expr *expr, bool allow_optional, bool *no_match_ref,
						   bool as_binary)
{
	if (expr->expr_kind == EXPR_EMBED && allow_optional)
	{
		if (!sema_expr_analyse_embed(context, expr, true)) return false;
	}
	else
	{
		if (!sema_analyse_inferred_expr(context, to, expr, no_match_ref)) return false;
	}
	if (!sema_cast_rvalue(context, expr, true)) return false;
	if (to) to = type_no_optional(to);
	Type *to_canonical = to ? type_no_optional(to)->canonical : NULL;
	Type *rhs_type = expr->type;
	Type *rhs_type_canonical = rhs_type->canonical;
	// Let's have a better error on `return io::FILE_NOT_FOUND;` when the return type is not fault.
	if (to && allow_optional && to_canonical != rhs_type_canonical && rhs_type_canonical == type_fault)
	{
		Type *flat = type_flatten(to);
		if (flat != type_fault && sema_cast_const(expr))
		{
			if (no_match_ref) goto NO_MATCH_REF;
			print_error_after(expr->span, "You need to add a trailing '?' here to make this an optional.");
			return false;
		}
	}
	// Let's see if we have a fixed slice.
	if (to && type_is_arraylike(to_canonical) && expr->expr_kind == EXPR_SLICE && rhs_type_canonical->type_kind == TYPE_SLICE)
	{
		Type *element = type_get_indexed_type(rhs_type_canonical)->canonical;
		if (element != type_get_indexed_type(to_canonical)->canonical) goto NO_SLICE;
		IndexDiff len = range_const_len(&expr->slice_expr.range);
		if (len < 1) goto NO_SLICE;
		if (len != to_canonical->array.len)
		{
			if (no_match_ref) goto NO_MATCH_REF;
			RETURN_SEMA_ERROR(expr, "Slice length mismatch, expected %u but got %u.", to_canonical->array.len, len);
		}
		// Given x[3..7] -> (int[5]*)x[3..7]
		cast_no_check(expr, type_get_ptr(type_get_array(element, len)), IS_OPTIONAL(expr));
		// Deref
		if (!sema_expr_rewrite_insert_deref(context, expr)) return false;
		cast_no_check(expr, to, IS_OPTIONAL(expr));
		return true;
	}
	NO_SLICE:;
	if (to)
	{
		if (as_binary)
		{
			if (!cast_implicit_binary(context, expr, to, no_match_ref)) return false;
		}
		else
		{
			if (!cast_implicit_checked(context, expr, to, false, no_match_ref)) return false;
		}
	}
	if (!allow_optional && IS_OPTIONAL(expr))
	{
		if (no_match_ref) goto NO_MATCH_REF;
		RETURN_SEMA_ERROR(expr, "It is not possible to cast from %s to %s.", type_quoted_error_string(expr->type),
				   type_quoted_error_string(type_no_optional(expr->type)));
	}
	return true;
NO_MATCH_REF:
	*no_match_ref = true;
	return false;
}

static inline bool sema_cast_ct_ident_rvalue(SemaContext *context, Expr *expr)
{
	Decl *decl = expr->ct_ident_expr.decl;
	Expr *copy = copy_expr_single(decl->var.init_expr);
	if (!copy)
	{
		RETURN_SEMA_ERROR(expr, "'%s' was not yet initialized to any value, assign a value to it before use.", decl->name);
	}
	if (!sema_analyse_expr_rvalue(context, copy)) return false;
	sema_cast_const(copy);
	expr_replace(expr, copy);
	return true;
}

static inline bool sema_cast_rvalue(SemaContext *context, Expr *expr, bool mutate)
{
	if (!expr_ok(expr)) return false;
	switch (expr->expr_kind)
	{
		case EXPR_MEMBER_SET:
			RETURN_SEMA_ERROR(expr, "Expected two parameters to 'set', e.g. '$member.set(v, new_value)'.");
		case EXPR_MEMBER_GET:
			RETURN_SEMA_ERROR(expr, "Expected a parameter to 'get', e.g. '$member.get(v)'.");
		case EXPR_MACRO_BODY_EXPANSION:
			if (!expr->body_expansion_expr.first_stmt)
			{
				RETURN_SEMA_ERROR(expr, "'@%s' must be followed by ().", declptr(context->current_macro->func_decl.body_param)->name); // NOLINT
			}
			break;
		case EXPR_TYPECALL:
			RETURN_SEMA_ERROR(expr, "A tag name must be given.");
		case EXPR_BUILTIN:
			RETURN_SEMA_ERROR(expr, "A builtin must be followed by ().");
		case EXPR_ACCESS_RESOLVED:
			if (expr->access_resolved_expr.ref->decl_kind == DECL_FUNC)
			{
				RETURN_SEMA_ERROR(expr, "A function name must be followed by '(' or preceded by '&'.");
			}
			if (expr->access_resolved_expr.ref->decl_kind == DECL_MACRO)
			{
				RETURN_SEMA_ERROR(expr, "A macro name must be followed by '('.");
			}
			// We may have kept FOO.x.y as a reference, fold it now if y is not an aggregate.
			if (mutate) sema_expr_flatten_const_ident(expr->access_resolved_expr.parent);
			return true;
		case EXPR_TYPEINFO:
			expr_rewrite_const_typeid(expr, expr->type_expr->type);
			return true;
		case EXPR_CT_IDENT:
			if (mutate && !sema_cast_ct_ident_rvalue(context, expr)) return false;
			break;
		case EXPR_IDENTIFIER:
			if (mutate && !sema_cast_ident_rvalue(context, expr)) return false;
			break;
		case EXPR_SLICE:
		{
			Expr *inner = exprptr(expr->subscript_expr.expr);
			if (inner->expr_kind != EXPR_IDENTIFIER) break;
			Decl *decl = inner->ident_expr;
			if (decl->decl_kind != DECL_VAR) break;
			if (!decl->var.out_param || decl->var.in_param) break;
			if (context->active_scope.flags & (SCOPE_ENSURE | SCOPE_ENSURE_MACRO)) break;
			break;
		}
		case EXPR_SUBSCRIPT:
		{
			Expr *inner = exprptr(expr->subscript_expr.expr);
			if (inner->expr_kind != EXPR_IDENTIFIER) break;
			Decl *decl = inner->ident_expr;
			if (decl->decl_kind != DECL_VAR) break;
			if (!decl->var.out_param || decl->var.in_param) break;
			if (context->active_scope.flags & (SCOPE_ENSURE | SCOPE_ENSURE_MACRO)) break;
			RETURN_SEMA_ERROR(expr, "'out' parameters may not be read.");
		}
		case EXPR_UNARY:
		{
			if (expr->unary_expr.operator != UNARYOP_DEREF) break;
			Expr *inner = expr->inner_expr;
			if (inner->expr_kind != EXPR_IDENTIFIER) break;
			Decl *decl = inner->ident_expr;
			if (decl->decl_kind != DECL_VAR) break;
			if (expr->unary_expr.no_read) break;
			if (!decl->var.out_param || decl->var.in_param) break;
			if (context->active_scope.flags & (SCOPE_ENSURE | SCOPE_ENSURE_MACRO)) return true;
			RETURN_SEMA_ERROR(expr, "'out' parameters may not be read.");
		}
		default:
			break;
	}
	return true;
}

bool sema_analyse_ct_expr(SemaContext *context, Expr *expr)
{
	if (!sema_analyse_expr(context, expr)) return false;
	if (expr->expr_kind == EXPR_TYPEINFO)
	{
		Type *cond_val = expr->type_expr->type;
		expr->expr_kind = EXPR_CONST;
		expr->const_expr.const_kind = CONST_TYPEID;
		expr->const_expr.typeid = cond_val->canonical;
		expr->type = type_typeid;
	}
	if (!sema_cast_rvalue(context, expr, true)) return false;
	if (!sema_cast_const(expr))
	{
		RETURN_SEMA_ERROR(expr, "Expected a compile time expression.");
	}
	return true;
}

bool sema_analyse_expr(SemaContext *context, Expr *expr)
{
	RESOLVE(expr, sema_analyse_expr_dispatch(context, expr))
}

static inline bool sema_analyse_expr_lvalue_dispatch(SemaContext *context, Expr *expr, bool *failed_ref)
{
RETRY:
	switch (expr->expr_kind)
	{
		case EXPR_HASH_IDENT:
			DEBUG_LOG("Expand hash ident");
			if (!sema_expr_fold_hash(context, expr)) return false;
			goto RETRY;
		case EXPR_CT_IDENT:
			return sema_expr_resolve_ct_identifier(context, expr);
		case EXPR_SUBSCRIPT:
			return sema_expr_analyse_subscript_lvalue(context, expr, failed_ref);
		case EXPR_OTHER_CONTEXT:
		{
			DEBUG_LOG("Switch context");
			SemaContext *new_context = expr->expr_other_context.context;
			expr_replace(expr, expr->expr_other_context.inner);
			if (expr->resolve_status == RESOLVE_DONE) return expr_ok(expr);
			ContextSwitchState state = context_switch_state_push(context, new_context);
			expr->resolve_status = RESOLVE_RUNNING;
			bool success = sema_analyse_expr_lvalue_dispatch(new_context, expr, failed_ref);
			context_switch_stat_pop(new_context, state);
			return success;
		}
		case EXPR_SWIZZLE:
			if (failed_ref) goto FAILED_REF;
			RETURN_SEMA_ERROR(expr, "You cannot use swizzling to assign to multiple elements, use element-wise assign instead.");
		case EXPR_LAMBDA:
			if (failed_ref) goto FAILED_REF;
			RETURN_SEMA_ERROR(expr, "This expression is a value and cannot be assigned to.");
		case EXPR_CONST:
			if (failed_ref) goto FAILED_REF;
			RETURN_SEMA_ERROR(expr, "You cannot assign to a constant expression.");
		case EXPR_GENERIC_IDENT:
			if (!sema_analyse_expr_dispatch(context, expr)) return false;
			goto IDENT_CHECK;
		case EXPR_UNRESOLVED_IDENTIFIER:
			if (!sema_analyse_expr_dispatch(context, expr)) return false;
			goto RETRY;
		case EXPR_IDENTIFIER:
IDENT_CHECK:;
		{
			Decl *decl = expr->ident_expr;
			if (decl->decl_kind != DECL_VAR)
			{
				if (failed_ref) goto FAILED_REF;
				RETURN_SEMA_ERROR(expr, "You cannot assign a value to %s.", decl_to_a_name(decl));
			}
			if (decl->var.kind == VARDECL_CONST)
			{
				if (failed_ref) goto FAILED_REF;
				RETURN_SEMA_ERROR(expr, "You cannot assign to a constant.");
			}
			decl = decl_raw(decl);
			switch (decl->var.kind)
			{
				case VARDECL_LOCAL_CT:
				case VARDECL_LOCAL_CT_TYPE:
				case VARDECL_LOCAL:
				case VARDECL_GLOBAL:
				case VARDECL_PARAM:
				case VARDECL_PARAM_CT:
				case VARDECL_PARAM_CT_TYPE:
					return true;
				case VARDECL_MEMBER:
				case VARDECL_BITMEMBER:
					break;
				case VARDECL_CONST:
				case VARDECL_PARAM_EXPR:
				case VARDECL_UNWRAPPED:
				case VARDECL_ERASE:
				case VARDECL_REWRAPPED:
					UNREACHABLE
			}
			UNREACHABLE
		}
		case EXPR_POISONED:
			return false;
		case EXPR_UNARY:
			if (!sema_analyse_expr_dispatch(context, expr)) return false;
			if (expr->unary_expr.operator != UNARYOP_DEREF) break;
			if (IS_OPTIONAL(expr))
			{
				if (failed_ref) goto FAILED_REF;
				RETURN_SEMA_ERROR(expr, "You cannot assign to a dereferenced optional.");
			}
			return true;
		case EXPR_CT_ARG:
			if (expr->ct_arg_expr.type == TOKEN_CT_VAEXPR)
			{
				if (!context->current_macro)
				{
					RETURN_SEMA_ERROR(expr, "'$vaexpr' can only be used inside of a macro.");
				}
				// An expr argument, this means we copy and evaluate.
				ASSIGN_EXPR_OR_RET(Expr *arg_expr, sema_expr_analyse_ct_arg_index(context, exprptr(expr->ct_arg_expr.arg), NULL), false);
				expr_replace(expr, copy_expr_single(arg_expr));
				return sema_analyse_expr_lvalue(context, expr, failed_ref);
			}
			break;
		case EXPR_RECAST:
		case EXPR_CAST:
			if (failed_ref) goto FAILED_REF;
			RETURN_SEMA_ERROR(expr, "Assignment to casts is not possible (this sub-expression is a value, not an address), maybe you put the order of the operators wrong?");
		case EXPR_CT_EVAL:
			if (!sema_expr_resolve_ct_eval(context, expr)) return false;
			goto RETRY;
		case EXPR_SLICE:
			return sema_expr_analyse_slice(context, expr);
		case EXPR_ACCESS_UNRESOLVED:
			expr->access_unresolved_expr.is_lvalue = true;
			expr->access_unresolved_expr.is_ref = true;
			return sema_expr_analyse_access(context, expr, failed_ref);
		case EXPR_EXT_TRUNC:
		case EXPR_INT_TO_BOOL:
		case EXPR_DISCARD:
		case EXPR_ADDR_CONVERSION:
		case EXPR_ASM:
		case EXPR_BENCHMARK_HOOK:
		case EXPR_BINARY:
		case EXPR_BITASSIGN:
		case EXPR_BUILTIN:
		case EXPR_BUILTIN_ACCESS:
		case EXPR_CALL:
		case EXPR_CATCH:
		case EXPR_COMPILER_CONST:
		case EXPR_COND:
		case EXPR_CT_CALL:
		case EXPR_CT_ASSIGNABLE:
		case EXPR_CT_DEFINED:
		case EXPR_CT_IS_CONST:
		case EXPR_DECL:
		case EXPR_DEFAULT_ARG:
		case EXPR_DESIGNATED_INITIALIZER_LIST:
		case EXPR_DESIGNATOR:
		case EXPR_FORCE_UNWRAP:
		case EXPR_INITIALIZER_LIST:
		case EXPR_IOTA_DECL:
		case EXPR_LAST_FAULT:
		case EXPR_MACRO_BLOCK:
		case EXPR_MACRO_BODY_EXPANSION:
		case EXPR_MAKE_ANY:
		case EXPR_MAKE_SLICE:
		case EXPR_MEMBER_GET:
		case EXPR_MEMBER_SET:
		case EXPR_NAMED_ARGUMENT:
		case EXPR_NOP:
		case EXPR_OPERATOR_CHARS:
		case EXPR_OPTIONAL:
		case EXPR_POINTER_OFFSET:
		case EXPR_POST_UNARY:
		case EXPR_PTR_ACCESS:
		case EXPR_ENUM_FROM_ORD:
		case EXPR_SLICE_LEN:
		case EXPR_FLOAT_TO_INT:
		case EXPR_INT_TO_FLOAT:
		case EXPR_INT_TO_PTR:
		case EXPR_PTR_TO_INT:
		case EXPR_LENGTHOF:
		case EXPR_RETHROW:
		case EXPR_RETVAL:
		case EXPR_RVALUE:
		case EXPR_SLICE_ASSIGN:
		case EXPR_SLICE_COPY:
		case EXPR_SPLAT:
		case EXPR_STRINGIFY:
		case EXPR_TERNARY:
		case EXPR_TEST_HOOK:
		case EXPR_TRY:
		case EXPR_TRY_UNWRAP_CHAIN:
		case EXPR_TYPECALL:
		case EXPR_TYPEID_INFO:
		case EXPR_TYPEINFO:
		case EXPR_VECTOR_FROM_ARRAY:
		case EXPR_VECTOR_TO_ARRAY:
		case EXPR_SLICE_TO_VEC_ARRAY:
		case EXPR_SCALAR_TO_VECTOR:
		case EXPR_SUBSCRIPT_ADDR:
		case EXPR_EXPRESSION_LIST:
		case EXPR_MACRO_BODY:
		case EXPR_CATCH_UNRESOLVED:
		case EXPR_COMPOUND_LITERAL:
		case EXPR_EMBED:
		case EXPR_TYPEID:
		case EXPR_VASPLAT:
		case EXPR_TRY_UNRESOLVED:
		case EXPR_TWO:
			break;
		case EXPR_BITACCESS:
		case EXPR_SUBSCRIPT_ASSIGN:
		case EXPR_ACCESS_RESOLVED:
		case EXPR_CT_SUBSCRIPT:
			UNREACHABLE
	}
	if (failed_ref) goto FAILED_REF;
	RETURN_SEMA_ERROR(expr, "An assignable expression, like a variable, was expected here.");

FAILED_REF:
	*failed_ref = true;
	return false;
}

bool sema_analyse_expr_lvalue(SemaContext *context, Expr *expr, bool *failed_ref)
{
	ASSERT(expr);
	RESOLVE(expr, sema_analyse_expr_lvalue_dispatch(context, expr, failed_ref))
}

bool sema_expr_check_discard(SemaContext *context, Expr *expr)
{
	if (expr->expr_kind == EXPR_EXPRESSION_LIST)
	{
		FOREACH(Expr *, expr_element, expr->expression_list)
		{
			if (!sema_expr_check_discard(context, expr_element)) return false;
		}
		return true;
	}
	if (expr->expr_kind == EXPR_DECL) return true;
	if (expr->expr_kind == EXPR_SUBSCRIPT_ASSIGN || expr->expr_kind == EXPR_SLICE_ASSIGN) return true;
	if (expr->expr_kind == EXPR_BINARY && expr->binary_expr.operator >= BINARYOP_ASSIGN) return true;
	if (expr->expr_kind == EXPR_UNARY || expr->expr_kind == EXPR_POST_UNARY)
	{
		switch (expr->unary_expr.operator)
		{
			case UNARYOP_DEC:
			case UNARYOP_INC:
				return true;
			default:
				break;
		}
	}
	if (expr->expr_kind == EXPR_MACRO_BLOCK)
	{
		if (expr->macro_block.is_must_use)
		{
			if (expr->macro_block.is_optional_return)
			{
				RETURN_SEMA_ERROR(expr, "The macro returns %s, which is an optional and must be handled. "
										"You can either assign it to a variable, rethrow it using '!', "
										"panic with '!!', use if-catch etc. You can also silence the error using a void cast (e.g. '(void)the_call()') to ignore the error.",
								  type_quoted_error_string(expr->type));
			}
			RETURN_SEMA_ERROR(expr, "The called macro is marked `@nodiscard` meaning the result should be kept. You can still discard it using a void cast (e.g. '(void)the_call()') if you want.");
		}
		if (expr->macro_block.had_optional_arg) goto ERROR_ARGS;
		return true;
	}
	if (expr->expr_kind == EXPR_CALL)
	{
		if (expr->call_expr.must_use)
		{
			if (expr->call_expr.is_optional_return)
			{
				RETURN_SEMA_ERROR(expr, "The function returns %s, which is an optional and must be handled. "
										"You can either assign it to a variable, rethrow it using '!', "
										"panic with '!!', use if-catch etc. You can also silence the error using a void cast (e.g. '(void)the_call()') to ignore the error.",
										type_quoted_error_string(expr->type));
			}
			RETURN_SEMA_ERROR(expr, "The called function is marked `@nodiscard` meaning the result should be kept. You can still discard it using a void cast (e.g. '(void)the_call()') if you want.");
		}
		if (expr->call_expr.has_optional_arg) goto ERROR_ARGS;
		return true;
	}
	if (!IS_OPTIONAL(expr))
	{
		if (expr->expr_kind == EXPR_BINARY && expr->binary_expr.operator == BINARYOP_EQ)
		{
			RETURN_SEMA_ERROR(expr, "This equals check was discarded, which isn't allowed. You can assign it to a variable or explicitly ignore it with a void cast '(void)' if this is what you want.");
		}
		return true;
	}
	RETURN_SEMA_ERROR(expr, "An optional value was discarded, you can assign it to a variable, ignore it with a void cast '(void)', rethrow on optional with '!' or panic '!!' to avoid this error.");
ERROR_ARGS:
	RETURN_SEMA_ERROR(expr, "The result of this call is optional due to its argument(s). This optional result may not be implicitly discarded. Please assign it to a variable, ignore it with '(void)', rethrow with '!' or panic with '!!'.");
}

bool sema_analyse_expr_rvalue(SemaContext *context, Expr *expr)
{
	return sema_analyse_expr(context, expr) && sema_cast_rvalue(context, expr, true);
}

bool sema_cast_const(Expr *expr)
{
	ASSERT_SPAN(expr, expr->resolve_status == RESOLVE_DONE);
	switch (expr->expr_kind)
	{
		case UNRESOLVED_EXPRS:
			UNREACHABLE
		case EXPR_RECAST:
			if (sema_cast_const(expr->inner_expr))
			{
				Type *type = expr->type;
				expr_replace(expr, expr->inner_expr);
				expr->type = type;
				return true;
			}
			return false;
		case EXPR_ACCESS_RESOLVED:
		case EXPR_BITACCESS:
		{
			Expr *parent = expr->access_resolved_expr.parent;
			Type *flat = type_flatten(parent->type);
			if (!sema_cast_const(parent)) return false;
			switch (flat->type_kind)
			{
				case TYPE_UNION:
				case TYPE_UNTYPED_LIST:
				case TYPE_STRUCT:
					break;
				case TYPE_BITSTRUCT:
					if (!expr_is_const_initializer(parent)) return false;
					break;
				default:
					return false;
			}
			if (!sema_expr_fold_to_member(expr, parent, expr->access_resolved_expr.ref)) return false;
			return true;
		}
		case EXPR_SLICE:
			return false;
		case EXPR_SUBSCRIPT:
		{
			if (!expr_is_const(exprptr(expr->subscript_expr.index.expr))) return false;
			Expr *parent = exprptr(expr->subscript_expr.expr);
			Type *flat_type = type_flatten(parent->type);
			if (!type_is_any_arraylike(flat_type) || flat_type->type_kind == TYPE_UNTYPED_LIST) return false;
			if (!sema_cast_const(parent)) return false;
			return sema_expr_fold_to_index(expr, parent, expr->subscript_expr.index);
		}
		case EXPR_IDENTIFIER:
			sema_expr_flatten_const_ident(expr);
			return expr_is_const(expr);
		case EXPR_CONST:
			return true;
		default:
			return false;
	}
	UNREACHABLE
}

bool sema_analyse_inferred_expr(SemaContext *context, Type *to, Expr *expr, bool *no_match_ref)
{
	Type *original_type = to;
	to = type_no_optional(to);
RETRY:
	switch (expr->resolve_status)
	{
		case RESOLVE_NOT_DONE:
			break;
		case RESOLVE_RUNNING:
			SEMA_ERROR(expr, "Recursive resolution of list.");
			return expr_poison(expr);
		case RESOLVE_DONE:
			if (to && expr->type != to)
			{
				cast_implicit_silent(context, expr, to, false);
			}
			return expr_ok(expr);
		default:
			UNREACHABLE
	}

	expr->resolve_status = RESOLVE_RUNNING;
	switch (expr->expr_kind)
	{
		case EXPR_HASH_IDENT:
			if (!sema_expr_fold_hash(context, expr)) return false;
			goto RETRY;
		case EXPR_OTHER_CONTEXT:
		{
			InliningSpan *new_span = context->inlined_at;
			context = expr->expr_other_context.context;
			InliningSpan *old_span = context->inlined_at;
			context->inlined_at = new_span;
			expr_replace(expr, expr->expr_other_context.inner);
			bool success = sema_analyse_inferred_expr(context, original_type, expr, no_match_ref);
			context->inlined_at = old_span;
			return success;
		}
		case EXPR_DESIGNATED_INITIALIZER_LIST:
		case EXPR_INITIALIZER_LIST:
			if (!sema_expr_analyse_initializer_list(context, to, expr, no_match_ref)) return expr_poison(expr);
			break;
		case EXPR_UNRESOLVED_IDENTIFIER:
			if (!sema_expr_analyse_identifier(context, to, expr)) return expr_poison(expr);
			break;
		case EXPR_LAMBDA:
			if (!sema_expr_analyse_lambda(context, to, expr)) return expr_poison(expr);
			break;
		case EXPR_BINARY:
			if (!sema_expr_analyse_binary(context, to, expr, NULL)) return expr_poison(expr);
			break;
		case EXPR_TERNARY:
			if (!sema_expr_analyse_ternary(context, to, expr)) return expr_poison(expr);
			break;
		case EXPR_CT_ARG:
			if (!sema_expr_analyse_ct_arg(context, to, expr, no_match_ref)) return expr_poison(expr);
			break;
		case EXPR_RETHROW:
			if (!sema_expr_analyse_rethrow(context, expr, original_type)) return expr_poison(expr);
			break;
		case EXPR_CT_EVAL:
			if (!sema_expr_resolve_ct_eval(context, expr)) return expr_poison(expr);
			goto RETRY;
		case EXPR_UNARY:
			if (to && expr->unary_expr.operator == UNARYOP_TADDR && to->canonical->type_kind == TYPE_POINTER && to->canonical != type_voidptr)
			{
				if (!sema_analyse_inferred_expr(context, type_get_indexed_type(to), expr->unary_expr.expr, NULL)) return expr_poison(expr);
			}
			FALLTHROUGH;
		default:
			if (!sema_analyse_expr_dispatch(context, expr)) return expr_poison(expr);
			break;
	}
	if (!sema_cast_rvalue(context, expr, true)) return false;
	expr->resolve_status = RESOLVE_DONE;
	return true;
}

TokenType sema_splitpathref(const char *string, ArraySize len, Path **path_ref, const char **ident_ref)
{
	ArraySize path_end = 0;
	*path_ref = NULL;
	*ident_ref = NULL;
	for (ArraySize i = 0; i < len; i++)
	{
		char ch = string[i];
		if (!char_is_alphanum_(ch))
		{
			if (ch == ':' && i > 0 && string[i + 1] == ':')
			{
				path_end = i;
				i++;
			}
			else
			{
				if (ch == '$' || ch == '@' || ch == '#') break; // $foo / @foo

				return TOKEN_INVALID_TOKEN;
			}
		}
	}
	if (path_end > 0)
	{
		*path_ref = path_create_from_string(string, path_end, INVALID_SPAN);
		string += path_end + 2;
		len -= path_end + 2;
	}
	while (len > 0)
	{
		char c = string[0];
		if (c != ' ' && c != '\t') break;
		len--;
		string++;
	}
	if (len == 0) return TOKEN_INVALID_TOKEN;
	uint32_t hash = FNV1_SEED;
	size_t start = 0;
	switch (string[0])
	{
		case '@':
		case '$':
		case '#':
			hash = FNV1a(string[0], hash);
			start = 1;
			break;
		default:
			break;
	}

	for (size_t i = start; i < len; i++)
	{
		char c = string[i];
		if (!char_is_alphanum_(c)) return TOKEN_INVALID_TOKEN;
		hash = FNV1a(c, hash);
	}
	TokenType type = TOKEN_INVALID_TOKEN;
	*ident_ref = symtab_find(string, len, hash, &type);
	if (!*ident_ref) return TOKEN_IDENT;
	switch (type)
	{
		case TOKEN_TYPE_IDENT:
		case TOKEN_IDENT:
		case TOKEN_CONST_IDENT:
		case TOKEN_AT_IDENT:
		case TOKEN_CT_TYPE_IDENT:
		case TOKEN_CT_IDENT:
		case TOKEN_HASH_IDENT:
		case TYPE_TOKENS:
			return type;
		default:
			*ident_ref = NULL;
			return TOKEN_INVALID_TOKEN;
	}
}


/*
 * Rewrite an expression into an expression call.
 */
bool sema_insert_method_call(SemaContext *context, Expr *method_call, Decl *method_decl, Expr *parent, Expr **arguments, bool reverse_overload)
{
	SourceSpan original_span = method_call->span;
	Expr *resolve = method_call;
	// In this case we need to resolve the second argument first.
	if (reverse_overload)
	{
		if (!expr_is_const(parent))
		{
			Decl *temp = decl_new_generated_var(method_decl->func_decl.signature.params[1]->type, VARDECL_LOCAL, parent->span);
			Expr *generate = expr_generate_decl(temp, expr_copy(parent));
			parent->expr_kind = EXPR_IDENTIFIER;
			parent->ident_expr = temp;
			parent->resolve_status = RESOLVE_DONE;
			parent->type = temp->type;
			if (!sema_analyse_expr_rvalue(context, generate)) return false;
			Expr *copied_method = expr_copy(method_call);
			expr_rewrite_two(method_call, generate, copied_method);
			method_call = copied_method;
		}
		Expr *arg0 = arguments[0];
		arguments[0] = parent;
		parent = arg0;
	}
	*method_call = (Expr) { .expr_kind = EXPR_CALL,
			.span = original_span,
			.resolve_status = RESOLVE_RUNNING,
			.call_expr.func_ref = declid(method_decl),
			.call_expr.arguments = arguments,
			.call_expr.is_func_ref = true,
			.call_expr.is_type_method = true,
	};
	Type *type = type_no_optional(parent->type)->canonical;
	Decl *first_param = method_decl->func_decl.signature.params[0];
	Type *first = first_param->type->canonical;
	// Deref / addr as needed.
	if (type != first)
	{
		if (first->type_kind == TYPE_POINTER && first->pointer == type)
		{
			expr_insert_addr(parent);
		}
		else if (type->type_kind == TYPE_POINTER && type->pointer == first)
		{
			if (!sema_expr_rewrite_insert_deref(context, parent)) return false;
		}
	}
	ASSERT_SPAN(method_call, first == type_no_optional(parent->type)->canonical);
	unit_register_external_symbol(context, method_decl);
	if (!sema_expr_analyse_general_call(context, method_call, method_decl, parent, false,
										NULL)) return expr_poison(method_call);
	method_call->resolve_status = RESOLVE_DONE;
	if (resolve != method_call)
	{
		resolve->resolve_status = RESOLVE_DONE;
		resolve->type = method_call->type;
	}
	return true;
}

static inline bool sema_insert_binary_overload(SemaContext *context, Expr *expr, Decl *overload, Expr *lhs, Expr *rhs, bool reverse)
{
	Expr **args = NULL;
	vec_add(args, rhs);
	return sema_insert_method_call(context, expr, overload, lhs, args, reverse);
}

// Check if the assignment fits
bool sema_bit_assignment_check(SemaContext *context, Expr *right, Decl *member, bool *failed_ref)
{
	// Don't check non-consts and non integers.
	if (!sema_cast_const(right) || !type_is_integer(right->type)) return true;

	unsigned bits = member->var.end_bit - member->var.start_bit + 1;

	// If we have enough bits to fit, then we're done.
	if (bits >= type_bit_size(right->type) || int_is_zero(right->const_expr.ixx)) return true;

	if (int_bits_needed(right->const_expr.ixx) > bits)
	{
		if (failed_ref) return *failed_ref = true, false;
		RETURN_SEMA_ERROR(right, "This constant would be truncated if stored in the "
								 "bitstruct, do you need a wider bit range?");
	}
	return true;
}
